hanikamu-rate-limit 0.1.0 → 0.2.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/CHANGELOG.md +18 -0
- data/README.md +101 -8
- data/lib/hanikamu/rate_limit/mixin.rb +66 -15
- data/lib/hanikamu/rate_limit/rate_queue.rb +47 -16
- data/lib/hanikamu/rate_limit/version.rb +1 -1
- data/lib/hanikamu/rate_limit.rb +54 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9eb792d3f4dc535c9a07eee7d06e701265ac45494f2b809afb8701404c64f347
|
|
4
|
+
data.tar.gz: 505b42072d9a2ece067dd670c97abf03a4dc131fae7d3a73d105f2df8352007c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8dce476cdc58a439d23b3015581b17869fe964c5a49344a63c38cb34f817c29140adc6035c461776654e2a0c10dd95625a490afaf1e755d6a1f0e61e8ce607fa
|
|
7
|
+
data.tar.gz: c929411ce3f465454813d5a496fa9fdc3bdf4339ab0ac789b431b53e95515484531bc092507f8242b643a410c723215b3af8ee7b7c6fd442aa87e39ca5af9b3c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-02-11
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- Removed `limit_with`. Use `limit_method` with the `registry:` option instead.
|
|
8
|
+
- Before: `limit_with :execute, registry: :external_api`
|
|
9
|
+
- After: `limit_method :execute, registry: :external_api`
|
|
10
|
+
- `limit_method` now raises `ArgumentError` when called without `registry:` or `rate:`.
|
|
11
|
+
- `limit_method` raises `ArgumentError` when `registry:` is combined with any other option (`rate:`, `interval:`, `check_interval:`, or `max_wait_time:`).
|
|
12
|
+
- Removed `key_prefix` from `limit_method`. Registry-based limits derive their key automatically.
|
|
13
|
+
- Removed `key_prefix` from `register_limit`. Registry keys are now always derived internally from the registry name.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `register_temporary_limit(name, remaining:, reset:)` — dynamically override a registered limit with a fixed-window counter and TTL, based on API response headers.
|
|
18
|
+
- Override-exhausted requests raise `RateLimitError` immediately when the remaining TTL exceeds `max_wait_time`, instead of polling.
|
|
19
|
+
- Input validation for `register_temporary_limit` — returns `false` for nil, negative, zero, or non-numeric values.
|
|
20
|
+
|
|
3
21
|
## 0.1.0 - 2026-02-04
|
|
4
22
|
|
|
5
23
|
- Initial release of Hanikamu::RateLimit.
|
data/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Requires Ruby 4.0 or later.
|
|
|
32
32
|
|
|
33
33
|
```ruby
|
|
34
34
|
# Gemfile
|
|
35
|
-
gem "hanikamu-rate-limit", "~> 0.
|
|
35
|
+
gem "hanikamu-rate-limit", "~> 0.2.0"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```bash
|
|
@@ -84,7 +84,8 @@ Registered limit options:
|
|
|
84
84
|
|
|
85
85
|
- `rate` and `interval` (required).
|
|
86
86
|
- `check_interval`, `max_wait_time` (optional).
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
`key_prefix` is no longer configurable for registered limits; registry keys are derived from the registry name.
|
|
88
89
|
|
|
89
90
|
## Usage
|
|
90
91
|
|
|
@@ -94,13 +95,21 @@ Optional per-method overrides:
|
|
|
94
95
|
limit_method :execute, rate: 5, interval: 1.0, check_interval: 0.1, max_wait_time: 3.0
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
Optional block called each time the limiter sleeps:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
limit_method :execute, rate: 5, interval: 1.0 do |sleep_time|
|
|
102
|
+
Rails.logger.info("Rate limited, sleeping #{sleep_time}s")
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
97
106
|
Use a registered limit shared across classes:
|
|
98
107
|
|
|
99
108
|
```ruby
|
|
100
109
|
class ExternalApiClient
|
|
101
110
|
extend Hanikamu::RateLimit::Mixin
|
|
102
111
|
|
|
103
|
-
|
|
112
|
+
limit_method :execute, registry: :external_api
|
|
104
113
|
|
|
105
114
|
def execute
|
|
106
115
|
# work
|
|
@@ -108,11 +117,13 @@ class ExternalApiClient
|
|
|
108
117
|
end
|
|
109
118
|
```
|
|
110
119
|
|
|
120
|
+
You must provide either `registry:` or `rate:` — combining them raises `ArgumentError`.
|
|
121
|
+
When `registry:` is used, it must be the only limit-related option (no `rate:`, `interval:`, `check_interval:`, or `max_wait_time:` overrides).
|
|
122
|
+
|
|
111
123
|
Registry precedence (highest to lowest):
|
|
112
124
|
|
|
113
|
-
1.
|
|
114
|
-
2.
|
|
115
|
-
3. Global defaults from `Hanikamu::RateLimit.configure`.
|
|
125
|
+
1. Registered limit options.
|
|
126
|
+
2. Global defaults from `Hanikamu::RateLimit.configure`.
|
|
116
127
|
|
|
117
128
|
Reset method is generated automatically:
|
|
118
129
|
|
|
@@ -120,6 +131,88 @@ Reset method is generated automatically:
|
|
|
120
131
|
MyService.reset_execute_limit!
|
|
121
132
|
```
|
|
122
133
|
|
|
134
|
+
### Dynamic overrides
|
|
135
|
+
|
|
136
|
+
Dynamic overrides only apply to **registry-based limits** (methods using `limit_method` with `registry:`). Methods limited with inline `rate:` / `interval:` options are not affected.
|
|
137
|
+
|
|
138
|
+
When an external API returns rate-limit headers (e.g. `X-RateLimit-Remaining`, `X-RateLimit-Reset`), you can temporarily override a registered limit to match the real window:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
Hanikamu::RateLimit.register_temporary_limit(:external_api, remaining: 175, reset: 60)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This stores a Redis counter with `remaining` requests allowed and a TTL of `reset` seconds. While the override is active, the fixed-window counter is used instead of the sliding window. When the TTL expires, the original registered limit resumes automatically.
|
|
145
|
+
|
|
146
|
+
Behavior when the override is exhausted (`remaining` reaches 0):
|
|
147
|
+
|
|
148
|
+
- If the remaining TTL exceeds `max_wait_time`, a `RateLimitError` is raised **immediately** — no polling occurs because the fixed-window quota won't reset until the TTL expires.
|
|
149
|
+
- If the remaining TTL is within `max_wait_time`, the limiter polls until the override expires and falls back to the sliding window.
|
|
150
|
+
|
|
151
|
+
This differs from the sliding window, which always polls in short intervals since entries continuously slide out of the window.
|
|
152
|
+
|
|
153
|
+
Typical usage in an API client:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
class ExternalApiClient
|
|
157
|
+
extend Hanikamu::RateLimit::Mixin
|
|
158
|
+
|
|
159
|
+
limit_method :call, registry: :external_api
|
|
160
|
+
|
|
161
|
+
def call
|
|
162
|
+
response = http_client.get("/endpoint")
|
|
163
|
+
|
|
164
|
+
if response.headers["X-RateLimit-Remaining"]
|
|
165
|
+
Hanikamu::RateLimit.register_temporary_limit(
|
|
166
|
+
:external_api,
|
|
167
|
+
remaining: response.headers["X-RateLimit-Remaining"],
|
|
168
|
+
reset: response.headers["X-RateLimit-Reset"]
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
response
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Class methods
|
|
178
|
+
|
|
179
|
+
To rate limit class methods, apply the mixin to the singleton class:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
class MyService
|
|
183
|
+
class << self
|
|
184
|
+
extend Hanikamu::RateLimit::Mixin
|
|
185
|
+
|
|
186
|
+
limit_method :call, rate: 5, interval: 1.0
|
|
187
|
+
|
|
188
|
+
def call
|
|
189
|
+
# work
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
You can also use registered limits:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
Hanikamu::RateLimit.configure do |config|
|
|
199
|
+
config.register_limit(:external_api, rate: 5, interval: 1.0)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class MyService
|
|
203
|
+
class << self
|
|
204
|
+
extend Hanikamu::RateLimit::Mixin
|
|
205
|
+
|
|
206
|
+
limit_method :call, registry: :external_api
|
|
207
|
+
|
|
208
|
+
def call
|
|
209
|
+
# work
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
|
|
123
216
|
## Error Handling
|
|
124
217
|
|
|
125
218
|
If Redis is unavailable, `RateQueue#shift` logs a warning and returns `nil`.
|
|
@@ -127,13 +220,13 @@ If Redis is unavailable, `RateQueue#shift` logs a warning and returns `nil`.
|
|
|
127
220
|
## Testing
|
|
128
221
|
|
|
129
222
|
```bash
|
|
130
|
-
|
|
223
|
+
make rspec
|
|
131
224
|
```
|
|
132
225
|
|
|
133
226
|
## Development
|
|
134
227
|
|
|
135
228
|
```bash
|
|
136
|
-
|
|
229
|
+
make shell
|
|
137
230
|
```
|
|
138
231
|
|
|
139
232
|
## License
|
|
@@ -3,36 +3,87 @@
|
|
|
3
3
|
module Hanikamu
|
|
4
4
|
module RateLimit
|
|
5
5
|
module Mixin
|
|
6
|
-
def limit_method(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
def limit_method(
|
|
7
|
+
method,
|
|
8
|
+
registry: nil,
|
|
9
|
+
rate: nil,
|
|
10
|
+
interval: nil,
|
|
11
|
+
check_interval: nil,
|
|
12
|
+
max_wait_time: nil,
|
|
13
|
+
&
|
|
14
|
+
)
|
|
15
|
+
if registry
|
|
16
|
+
validate_registry_only!(rate, interval, check_interval, max_wait_time)
|
|
17
|
+
queue = build_queue_from_registry(method, registry, &)
|
|
18
|
+
else
|
|
19
|
+
validate_inline_options!(rate, interval)
|
|
20
|
+
interval ||= 60
|
|
21
|
+
queue = build_queue(
|
|
22
|
+
rate,
|
|
23
|
+
interval,
|
|
24
|
+
method,
|
|
25
|
+
check_interval: check_interval,
|
|
26
|
+
max_wait_time: max_wait_time,
|
|
27
|
+
&
|
|
28
|
+
)
|
|
29
|
+
end
|
|
10
30
|
|
|
11
|
-
def limit_with(method, registry:, **overrides, &)
|
|
12
|
-
registry_config = Hanikamu::RateLimit.fetch_limit(registry)
|
|
13
|
-
merged = registry_config.merge(overrides.compact)
|
|
14
|
-
rate = merged.fetch(:rate)
|
|
15
|
-
interval = merged.fetch(:interval)
|
|
16
|
-
options = merged.slice(:check_interval, :max_wait_time, :key_prefix)
|
|
17
|
-
queue = build_queue(rate, interval, method, options, &)
|
|
18
31
|
install_rate_limited_method(method, queue)
|
|
19
32
|
end
|
|
20
33
|
|
|
21
34
|
private
|
|
22
35
|
|
|
23
|
-
def build_queue(
|
|
36
|
+
def build_queue(
|
|
37
|
+
rate,
|
|
38
|
+
interval,
|
|
39
|
+
method,
|
|
40
|
+
key_prefix: nil,
|
|
41
|
+
check_interval: nil,
|
|
42
|
+
max_wait_time: nil,
|
|
43
|
+
override_key: nil,
|
|
44
|
+
&
|
|
45
|
+
)
|
|
24
46
|
Hanikamu::RateLimit::RateQueue.new(
|
|
25
47
|
rate,
|
|
26
48
|
interval: interval,
|
|
27
49
|
klass_name: name,
|
|
28
50
|
method: method,
|
|
29
|
-
key_prefix:
|
|
30
|
-
|
|
31
|
-
|
|
51
|
+
key_prefix: key_prefix,
|
|
52
|
+
override_key: override_key,
|
|
53
|
+
check_interval: check_interval.nil? ? Hanikamu::RateLimit.config.check_interval : check_interval,
|
|
54
|
+
max_wait_time: max_wait_time.nil? ? Hanikamu::RateLimit.config.max_wait_time : max_wait_time,
|
|
55
|
+
&
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_queue_from_registry(method, registry, &)
|
|
60
|
+
registry_config = Hanikamu::RateLimit.fetch_limit(registry)
|
|
61
|
+
rate = registry_config.fetch(:rate)
|
|
62
|
+
interval = registry_config.fetch(:interval)
|
|
63
|
+
build_queue(
|
|
64
|
+
rate,
|
|
65
|
+
interval,
|
|
66
|
+
method,
|
|
67
|
+
key_prefix: registry_config[:key_prefix],
|
|
68
|
+
check_interval: registry_config[:check_interval],
|
|
69
|
+
max_wait_time: registry_config[:max_wait_time],
|
|
70
|
+
override_key: Hanikamu::RateLimit.override_key_for(registry),
|
|
32
71
|
&
|
|
33
72
|
)
|
|
34
73
|
end
|
|
35
74
|
|
|
75
|
+
def validate_registry_only!(rate, interval, check_interval, max_wait_time)
|
|
76
|
+
return unless rate || interval || !check_interval.nil? || !max_wait_time.nil?
|
|
77
|
+
|
|
78
|
+
raise ArgumentError, "registry: must be used alone"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate_inline_options!(rate, _interval)
|
|
82
|
+
return if rate
|
|
83
|
+
|
|
84
|
+
raise ArgumentError, "Either registry: or rate: must be provided"
|
|
85
|
+
end
|
|
86
|
+
|
|
36
87
|
def install_rate_limited_method(method, queue)
|
|
37
88
|
mixin = Module.new do
|
|
38
89
|
rate_queue = queue
|
|
@@ -9,11 +9,30 @@ module Hanikamu
|
|
|
9
9
|
KEY_PREFIX = "hanikamu:rate_limit:rate_queue"
|
|
10
10
|
LUA_SCRIPT = <<~LUA
|
|
11
11
|
local key = KEYS[1]
|
|
12
|
+
local override_key = KEYS[2]
|
|
12
13
|
local now = tonumber(ARGV[1])
|
|
13
14
|
local interval = tonumber(ARGV[2])
|
|
14
15
|
local rate = tonumber(ARGV[3])
|
|
15
16
|
local member = ARGV[4]
|
|
16
17
|
|
|
18
|
+
if override_key and override_key ~= "" then
|
|
19
|
+
local override_val = redis.call("GET", override_key)
|
|
20
|
+
if override_val then
|
|
21
|
+
local remaining = tonumber(override_val)
|
|
22
|
+
if remaining then
|
|
23
|
+
local ttl = redis.call("TTL", override_key)
|
|
24
|
+
if ttl > 0 then
|
|
25
|
+
if remaining > 0 then
|
|
26
|
+
redis.call("DECR", override_key)
|
|
27
|
+
return {1, 0, 0}
|
|
28
|
+
else
|
|
29
|
+
return {0, ttl, 1}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
17
36
|
redis.call("ZREMRANGEBYSCORE", key, 0, now - interval)
|
|
18
37
|
local count = redis.call("ZCARD", key)
|
|
19
38
|
|
|
@@ -33,25 +52,39 @@ module Hanikamu
|
|
|
33
52
|
return {0, interval}
|
|
34
53
|
LUA
|
|
35
54
|
|
|
36
|
-
def initialize(
|
|
55
|
+
def initialize(
|
|
56
|
+
rate,
|
|
57
|
+
klass_name:,
|
|
58
|
+
method:,
|
|
59
|
+
interval: 60,
|
|
60
|
+
key_prefix: nil,
|
|
61
|
+
override_key: nil,
|
|
62
|
+
check_interval: nil,
|
|
63
|
+
max_wait_time: nil,
|
|
64
|
+
&block
|
|
65
|
+
)
|
|
37
66
|
@rate = rate
|
|
38
67
|
@interval = interval.to_f
|
|
39
68
|
@klass_name = klass_name
|
|
40
69
|
@method = method
|
|
41
|
-
@key_prefix =
|
|
42
|
-
@
|
|
43
|
-
@
|
|
70
|
+
@key_prefix = key_prefix
|
|
71
|
+
@override_key = override_key&.to_s
|
|
72
|
+
@check_interval = check_interval.nil? ? Hanikamu::RateLimit.config.check_interval : check_interval
|
|
73
|
+
@max_wait_time = max_wait_time.nil? ? Hanikamu::RateLimit.config.max_wait_time : max_wait_time
|
|
44
74
|
@block = block
|
|
45
75
|
end
|
|
46
76
|
|
|
47
77
|
def shift
|
|
48
78
|
start_time = current_time
|
|
49
|
-
|
|
50
79
|
loop do
|
|
51
|
-
allowed, sleep_time = attempt_shift(start_time)
|
|
80
|
+
allowed, sleep_time, is_override = attempt_shift(start_time)
|
|
52
81
|
|
|
53
82
|
return if allowed == 1
|
|
54
83
|
|
|
84
|
+
if is_override == 1 && @max_wait_time && sleep_time.to_f > @max_wait_time
|
|
85
|
+
raise Hanikamu::RateLimit::RateLimitError, "Max wait time exceeded"
|
|
86
|
+
end
|
|
87
|
+
|
|
55
88
|
handle_sleep(sleep_time)
|
|
56
89
|
end
|
|
57
90
|
rescue Redis::BaseError => e
|
|
@@ -72,6 +105,12 @@ module Hanikamu
|
|
|
72
105
|
end
|
|
73
106
|
end
|
|
74
107
|
|
|
108
|
+
def redis_keys
|
|
109
|
+
return [redis_key] if @override_key.nil? || @override_key.empty?
|
|
110
|
+
|
|
111
|
+
[redis_key, @override_key]
|
|
112
|
+
end
|
|
113
|
+
|
|
75
114
|
def attempt_shift(start_time)
|
|
76
115
|
now = current_time
|
|
77
116
|
elapsed = now - start_time
|
|
@@ -84,11 +123,7 @@ module Hanikamu
|
|
|
84
123
|
end
|
|
85
124
|
|
|
86
125
|
def eval_script(now, member)
|
|
87
|
-
redis.evalsha(
|
|
88
|
-
lua_sha,
|
|
89
|
-
keys: [redis_key],
|
|
90
|
-
argv: [now, @interval, @rate, member]
|
|
91
|
-
)
|
|
126
|
+
redis.evalsha(lua_sha, keys: redis_keys, argv: [now, @interval, @rate, member])
|
|
92
127
|
rescue Redis::CommandError => e
|
|
93
128
|
return reload_script_and_retry(now, member) if e.message.include?("NOSCRIPT")
|
|
94
129
|
|
|
@@ -97,11 +132,7 @@ module Hanikamu
|
|
|
97
132
|
|
|
98
133
|
def reload_script_and_retry(now, member)
|
|
99
134
|
@lua_sha = redis.script(:load, LUA_SCRIPT)
|
|
100
|
-
redis.evalsha(
|
|
101
|
-
lua_sha,
|
|
102
|
-
keys: [redis_key],
|
|
103
|
-
argv: [now, @interval, @rate, member]
|
|
104
|
-
)
|
|
135
|
+
redis.evalsha(lua_sha, keys: redis_keys, argv: [now, @interval, @rate, member])
|
|
105
136
|
end
|
|
106
137
|
|
|
107
138
|
def handle_sleep(sleep_time)
|
data/lib/hanikamu/rate_limit.rb
CHANGED
|
@@ -18,23 +18,61 @@ module Hanikamu
|
|
|
18
18
|
class << self
|
|
19
19
|
def configure(&block)
|
|
20
20
|
super do |config|
|
|
21
|
-
config.define_singleton_method(:register_limit) do |
|
|
22
|
-
|
|
21
|
+
config.define_singleton_method(:register_limit) do |
|
|
22
|
+
name,
|
|
23
|
+
rate:,
|
|
24
|
+
interval:,
|
|
25
|
+
check_interval: nil,
|
|
26
|
+
max_wait_time: nil
|
|
27
|
+
|
|
|
28
|
+
Hanikamu::RateLimit.register_limit(
|
|
29
|
+
name,
|
|
30
|
+
rate: rate,
|
|
31
|
+
interval: interval,
|
|
32
|
+
check_interval: check_interval,
|
|
33
|
+
max_wait_time: max_wait_time
|
|
34
|
+
)
|
|
23
35
|
end
|
|
24
36
|
block&.call(config)
|
|
25
37
|
end
|
|
26
38
|
end
|
|
27
39
|
|
|
28
|
-
def register_limit(name,
|
|
29
|
-
registry.register(
|
|
40
|
+
def register_limit(name, rate:, interval:, check_interval: nil, max_wait_time: nil)
|
|
41
|
+
registry.register(
|
|
42
|
+
normalize_name(name),
|
|
43
|
+
normalize_registry_options(
|
|
44
|
+
name,
|
|
45
|
+
rate: rate,
|
|
46
|
+
interval: interval,
|
|
47
|
+
check_interval: check_interval,
|
|
48
|
+
max_wait_time: max_wait_time
|
|
49
|
+
)
|
|
50
|
+
)
|
|
30
51
|
end
|
|
31
52
|
|
|
32
53
|
def fetch_limit(name)
|
|
33
54
|
registry.resolve(normalize_name(name))
|
|
34
|
-
rescue Dry::Container::Error
|
|
55
|
+
rescue Dry::Container::Error, KeyError
|
|
35
56
|
raise ArgumentError, "Unknown registered limit: #{name}"
|
|
36
57
|
end
|
|
37
58
|
|
|
59
|
+
def register_temporary_limit(name, remaining:, reset:)
|
|
60
|
+
fetch_limit(name) # raise if not registered
|
|
61
|
+
remaining_value = Integer(remaining, exception: false)
|
|
62
|
+
reset_value = Integer(reset, exception: false)
|
|
63
|
+
return false if remaining_value.nil? || reset_value.nil?
|
|
64
|
+
return false if remaining_value.negative? || reset_value <= 0
|
|
65
|
+
|
|
66
|
+
key = override_key_for(name)
|
|
67
|
+
redis_client.set(key, remaining_value, ex: reset_value)
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def override_key_for(name)
|
|
72
|
+
normalized = normalize_name(name)
|
|
73
|
+
"#{RateQueue::KEY_PREFIX}:registry:#{normalized}:override"
|
|
74
|
+
end
|
|
75
|
+
|
|
38
76
|
def reset_registry!
|
|
39
77
|
@registry = Dry::Container.new
|
|
40
78
|
end
|
|
@@ -46,16 +84,20 @@ module Hanikamu
|
|
|
46
84
|
private
|
|
47
85
|
|
|
48
86
|
def normalize_name(name)
|
|
49
|
-
name.
|
|
87
|
+
normalized = name.to_s.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/^_+|_+$/, "")
|
|
88
|
+
normalized.to_sym
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def redis_client
|
|
92
|
+
@redis_client ||= Redis.new(url: config.redis_url)
|
|
50
93
|
end
|
|
51
94
|
|
|
52
|
-
def normalize_registry_options(name,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
key_prefix = options[:key_prefix] || "#{RateQueue::KEY_PREFIX}:registry:#{name}"
|
|
95
|
+
def normalize_registry_options(name, rate:, interval:, check_interval:, max_wait_time:)
|
|
96
|
+
normalized = normalize_name(name)
|
|
97
|
+
key_prefix = "#{RateQueue::KEY_PREFIX}:registry:#{normalized}"
|
|
56
98
|
registry_options = { rate: rate, interval: interval, key_prefix: key_prefix }
|
|
57
|
-
registry_options[:check_interval] =
|
|
58
|
-
registry_options[:max_wait_time] =
|
|
99
|
+
registry_options[:check_interval] = check_interval unless check_interval.nil?
|
|
100
|
+
registry_options[:max_wait_time] = max_wait_time unless max_wait_time.nil?
|
|
59
101
|
registry_options
|
|
60
102
|
end
|
|
61
103
|
end
|