berater 0.9.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 476b59e3f1e27908f5c8a5097087e6c8c2384531e1284d79b9d89a73fbbb5840
4
- data.tar.gz: de50fdcab6ea7dc9520fcf1aefdd07b1f8775c0ce96a30e3f1d45b6ccfb7b00f
3
+ metadata.gz: 8b657c7eb3a69e868416ad2d149d6c35ab13989e77a16ece23217deaaa9fafb6
4
+ data.tar.gz: 30835e7c247da37543ea118067d0b61d16080399ff6b53e86783c53a94b8d560
5
5
  SHA512:
6
- metadata.gz: 2446e64f528a8ef1d37791922d37cddcebdf702f3c5beff2ef7ea9d611ce4cc6d6c40ac42e1ea826987a2eb2ceea3ddf97e420bb7369686b8239b260a1725c55
7
- data.tar.gz: 23dccad7e02cc425631ceeb1dfbc6960b94ac92c523d840a991917af02caaf39a7befc5887fe45631f565ddb2eddb4850e557348ab29427d3cbae1db89124c3c
6
+ metadata.gz: '04481aa9c3097930f66eb29c6e19e650309b882cf835c1f36cb3fa58524b957d1194b5acc6a83f9fdfd33b73f888261ff3cc3171d730fd9781d9cf768b2b0768'
7
+ data.tar.gz: e0cd1a220a418face72685b44d23dff0040dc5f3d94b8c0d82769123a1c387e709702e8f093cc9c0f600d002ea4c848ebd4f0a2e38a4368e743104fbee597f9d
@@ -7,12 +7,12 @@ module Berater
7
7
  options[:redis] || Berater.redis
8
8
  end
9
9
 
10
- def limit(capacity: nil, cost: 1, &block)
11
- capacity ||= @capacity
12
- lock = nil
10
+ def limit(**opts, &block)
11
+ opts[:capacity] ||= @capacity
12
+ opts[:cost] ||= 1
13
13
 
14
- Berater.middleware.call(self, capacity: capacity, cost: cost) do |limiter, **opts|
15
- lock = limiter.inner_limit(**opts)
14
+ lock = Berater.middleware.call(self, **opts) do |limiter, **opts|
15
+ limiter.inner_limit(**opts)
16
16
  end
17
17
 
18
18
  if block_given?
@@ -27,10 +27,24 @@ module Berater
27
27
  end
28
28
 
29
29
  protected def inner_limit(capacity:, cost:)
30
+ if capacity.is_a?(String)
31
+ # try casting
32
+ begin
33
+ capacity = Float(capacity)
34
+ rescue ArgumentError; end
35
+ end
36
+
30
37
  unless capacity.is_a?(Numeric) && capacity >= 0
31
38
  raise ArgumentError, "invalid capacity: #{capacity}"
32
39
  end
33
40
 
41
+ if cost.is_a?(String)
42
+ # try casting
43
+ begin
44
+ cost = Float(cost)
45
+ rescue ArgumentError; end
46
+ end
47
+
34
48
  unless cost.is_a?(Numeric) && cost >= 0 && cost < Float::INFINITY
35
49
  raise ArgumentError, "invalid cost: #{cost}"
36
50
  end
@@ -76,6 +90,13 @@ module Berater
76
90
  end
77
91
 
78
92
  def capacity=(capacity)
93
+ if capacity.is_a?(String)
94
+ # try casting
95
+ begin
96
+ capacity = Float(capacity)
97
+ rescue TypeError, ArgumentError; end
98
+ end
99
+
79
100
  unless capacity.is_a?(Numeric)
80
101
  raise ArgumentError, "expected Numeric, found #{capacity.class}"
81
102
  end
@@ -99,11 +120,16 @@ module Berater
99
120
  end
100
121
 
101
122
  class << self
102
- def new(*)
123
+ def new(*args, **kwargs)
103
124
  # can only call via subclass
104
125
  raise NoMethodError if self == Berater::Limiter
105
126
 
106
- super
127
+ if RUBY_VERSION < '3' && kwargs.empty?
128
+ # avoid ruby 2 problems with empty hashes
129
+ super(*args)
130
+ else
131
+ super
132
+ end
107
133
  end
108
134
 
109
135
  def cache_key(key)
@@ -116,6 +142,7 @@ module Berater
116
142
  def inherited(subclass)
117
143
  # automagically create convenience method
118
144
  name = subclass.to_s.split(':')[-1]
145
+
119
146
  Berater.define_singleton_method(name) do |*args, **opts, &block|
120
147
  Berater::Utils.convenience_fn(subclass, *args, **opts, &block)
121
148
  end
data/lib/berater/lock.rb CHANGED
@@ -20,7 +20,7 @@ module Berater
20
20
 
21
21
  @released_at = Time.now
22
22
  @release_fn&.call
23
- nil
23
+ true
24
24
  end
25
25
 
26
26
  end
@@ -1,4 +1,5 @@
1
1
  require 'digest'
2
+ require 'redis'
2
3
 
3
4
  module Berater
4
5
  class LuaScript
@@ -6,11 +7,11 @@ module Berater
6
7
  attr_reader :source
7
8
 
8
9
  def initialize(source)
9
- @source = source
10
+ @source = source.dup.freeze
10
11
  end
11
12
 
12
13
  def sha
13
- @sha ||= Digest::SHA1.hexdigest(minify)
14
+ @sha ||= Digest::SHA1.hexdigest(minify).freeze
14
15
  end
15
16
 
16
17
  def eval(redis, *args)
@@ -44,7 +45,7 @@ module Berater
44
45
  def minify
45
46
  # trim comments (whole line and partial)
46
47
  # and whitespace (prefix and empty lines)
47
- @minify ||= source.gsub(/^\s*--.*\n|\s*--.*|^\s*|^$\n/, '').chomp
48
+ @minify ||= source.gsub(/^\s*--.*\n|\s*--.*|^\s*|^$\n/, '').chomp.freeze
48
49
  end
49
50
 
50
51
  end
@@ -0,0 +1,41 @@
1
+ require 'set'
2
+
3
+ module Berater
4
+ module Middleware
5
+ class FailOpen
6
+ ERRORS = Set[
7
+ Redis::BaseConnectionError,
8
+ ]
9
+
10
+ def initialize(errors: nil, on_fail: nil)
11
+ @errors = errors || ERRORS
12
+ @on_fail = on_fail
13
+ end
14
+
15
+ def call(*, **opts)
16
+ yield.tap do |lock|
17
+ # wrap lock.release so it fails open
18
+
19
+ # save reference to original function
20
+ release_fn = lock.method(:release)
21
+
22
+ # make bound variables accessible to block
23
+ errors = @errors
24
+ on_fail = @on_fail
25
+
26
+ lock.define_singleton_method(:release) do
27
+ release_fn.call
28
+ rescue *errors => e
29
+ on_fail&.call(e)
30
+ false
31
+ end
32
+ end
33
+ rescue *@errors => e
34
+ @on_fail&.call(e)
35
+
36
+ # fail open by faking a lock
37
+ Berater::Lock.new(opts[:capacity], -1)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ module Berater
2
+ module Middleware
3
+ class LoadShedder
4
+ PRIORITY_RANGE = 1..5
5
+
6
+ def initialize(default_priority: nil)
7
+ @default_priority = default_priority
8
+ end
9
+
10
+ def call(*args, **opts)
11
+ if priority = opts.delete(:priority) || @default_priority
12
+ opts[:capacity] = adjust_capacity(opts[:capacity], priority)
13
+ end
14
+
15
+ yield *args, **opts
16
+ end
17
+
18
+ protected
19
+
20
+ def adjust_capacity(capacity, priority)
21
+ unless PRIORITY_RANGE.include?(priority)
22
+ return capacity
23
+ end
24
+
25
+ # priority 1 stays at 100%, 2 scales down to 90%, 5 to 60%
26
+ factor = 1 - (priority - 1) * 0.1
27
+ (capacity * factor).floor
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ module Berater
2
+ module Middleware
3
+ autoload 'FailOpen', 'berater/middleware/fail_open'
4
+ autoload 'LoadShedder', 'berater/middleware/load_shedder'
5
+ end
6
+ end
data/lib/berater/utils.rb CHANGED
@@ -44,12 +44,7 @@ module Berater
44
44
 
45
45
  def convenience_fn(klass, *args, **opts, &block)
46
46
  limiter = klass.new(*args, **opts)
47
- if block_given?
48
- limiter.limit(&block)
49
- else
50
- limiter
51
- end
47
+ block ? limiter.limit(&block) : limiter
52
48
  end
53
-
54
49
  end
55
50
  end
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.9.0'
2
+ VERSION = "0.11.1"
3
3
  end
data/lib/berater.rb CHANGED
@@ -2,6 +2,7 @@ require 'berater/limiter'
2
2
  require 'berater/limiter_set'
3
3
  require 'berater/lock'
4
4
  require 'berater/lua_script'
5
+ require 'berater/middleware'
5
6
  require 'berater/utils'
6
7
  require 'berater/version'
7
8
  require 'meddleware'
data/spec/berater_spec.rb CHANGED
@@ -59,8 +59,8 @@ describe Berater do
59
59
  end
60
60
 
61
61
  it 'accepts an optional redis parameter' do
62
- redis = double('Redis')
63
- limiter = Berater.new(:key, capacity, opts.merge(redis: redis))
62
+ redis = double(Redis)
63
+ limiter = Berater.new(:key, capacity, **opts.merge(redis: redis))
64
64
  expect(limiter.redis).to be redis
65
65
  end
66
66
  end
@@ -24,6 +24,7 @@ describe Berater::ConcurrencyLimiter do
24
24
  it { expect_capacity(0) }
25
25
  it { expect_capacity(1) }
26
26
  it { expect_capacity(1.5) }
27
+ it { expect_capacity('1.5') }
27
28
  it { expect_capacity(10_000) }
28
29
 
29
30
  context 'with erroneous values' do
@@ -34,7 +35,7 @@ describe Berater::ConcurrencyLimiter do
34
35
  end
35
36
 
36
37
  it { expect_bad_capacity(-1) }
37
- it { expect_bad_capacity('1') }
38
+ it { expect_bad_capacity('abc') }
38
39
  it { expect_bad_capacity(:one) }
39
40
  it { expect_bad_capacity(Float::INFINITY) }
40
41
  end
data/spec/limiter_spec.rb CHANGED
@@ -14,6 +14,32 @@ describe Berater::Limiter do
14
14
  end
15
15
  end
16
16
 
17
+ describe '#capacity=' do
18
+ subject { Berater::Unlimiter.new(:key, capacity).capacity }
19
+
20
+ context 'when capacity is numeric' do
21
+ let(:capacity) { 3.5 }
22
+
23
+ it { is_expected.to be capacity }
24
+ end
25
+
26
+ context 'when capacity is a stringified numeric' do
27
+ let(:capacity) { '3.5' }
28
+
29
+ it 'casts the value gracefully' do
30
+ is_expected.to be capacity.to_f
31
+ end
32
+ end
33
+
34
+ context 'when capacity is a bogus value' do
35
+ let(:capacity) { :abc }
36
+
37
+ it 'raises' do
38
+ expect { subject }.to raise_error(ArgumentError)
39
+ end
40
+ end
41
+ end
42
+
17
43
  describe '#limit' do
18
44
  subject { Berater::Unlimiter.new }
19
45
 
@@ -29,6 +55,12 @@ describe Berater::Limiter do
29
55
  subject.limit(capacity: 'abc')
30
56
  }.to raise_error(ArgumentError)
31
57
  end
58
+
59
+ it 'handles stringified numerics gracefully' do
60
+ is_expected.to receive(:acquire_lock).with(3.5, anything)
61
+
62
+ subject.limit(capacity: '3.5')
63
+ end
32
64
  end
33
65
 
34
66
  context 'with a cost parameter' do
@@ -51,6 +83,12 @@ describe Berater::Limiter do
51
83
  subject.limit(cost: Float::INFINITY)
52
84
  }.to raise_error(ArgumentError)
53
85
  end
86
+
87
+ it 'handles stringified numerics gracefully' do
88
+ is_expected.to receive(:acquire_lock).with(anything, 2.5)
89
+
90
+ subject.limit(cost: '2.5')
91
+ end
54
92
  end
55
93
 
56
94
  context 'when Berater.redis is nil' do
@@ -79,6 +117,16 @@ describe Berater::Limiter do
79
117
  expect { limiter.limit }.to raise_error(RuntimeError)
80
118
  end
81
119
  end
120
+
121
+ it 'releases the lock even when limited code raises an error' do
122
+ lock = Berater::Lock.new(Float::INFINITY, 0)
123
+ expect(subject).to receive(:acquire_lock).and_return(lock)
124
+ expect(lock).to receive(:release)
125
+
126
+ expect {
127
+ subject.limit { raise 'fail' }
128
+ }.to raise_error(RuntimeError)
129
+ end
82
130
  end
83
131
 
84
132
  describe '#==' do
@@ -93,5 +93,4 @@ describe Berater::LuaScript do
93
93
  it { subject }
94
94
  end
95
95
  end
96
-
97
96
  end
@@ -0,0 +1,184 @@
1
+ describe Berater::Middleware::FailOpen do
2
+ let(:limiter) { Berater::Unlimiter.new }
3
+ let(:lock) { limiter.limit }
4
+ let(:error) { Redis::TimeoutError }
5
+
6
+ describe '.call' do
7
+ let(:instance) { described_class.new(errors: errors, on_fail: on_fail) }
8
+ let(:errors) { nil }
9
+ let(:on_fail) { nil }
10
+
11
+ it 'returns the blocks value' do
12
+ expect(instance.call { lock }).to be lock
13
+ end
14
+
15
+ context 'when there is an error during lock acquisition' do
16
+ subject { instance.call { raise error } }
17
+
18
+ it 'still returns a lock' do
19
+ expect(subject).to be_a Berater::Lock
20
+ end
21
+
22
+ it 'creates a new, fake lock' do
23
+ expect(Berater::Lock).to receive(:new)
24
+ subject
25
+ end
26
+
27
+ it 'returns a lock that is releasable' do
28
+ expect(subject.release).to be true
29
+ end
30
+
31
+ context 'when an on_fail handler is defined' do
32
+ let(:on_fail) { double(Proc) }
33
+
34
+ it 'calls the handler' do
35
+ expect(on_fail).to receive(:call).with(error)
36
+ subject
37
+ end
38
+ end
39
+
40
+ context 'when the error is an IOError' do
41
+ let(:error) { IOError }
42
+
43
+ it 'would normally not catch the error' do
44
+ expect { subject }.to raise_error(error)
45
+ end
46
+
47
+ context 'and errors option is set' do
48
+ let(:errors) { [ error ] }
49
+
50
+ it 'catches the error' do
51
+ expect { subject }.not_to raise_error
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'when there is an error during lock release' do
58
+ subject { instance.call { lock }.release }
59
+
60
+ before do
61
+ expect(lock).to receive(:release).and_raise(error)
62
+ end
63
+
64
+ it 'handles the exception' do
65
+ expect { subject }.not_to raise_error
66
+ end
67
+
68
+ it 'returns false since lock was not released' do
69
+ is_expected.to be false
70
+ end
71
+
72
+ context 'when an on_fail handler is defined' do
73
+ let(:on_fail) { double(Proc) }
74
+
75
+ it 'calls the handler' do
76
+ expect(on_fail).to receive(:call).with(Exception)
77
+ subject
78
+ end
79
+ end
80
+
81
+ context 'when the error is an IOError' do
82
+ let(:error) { IOError }
83
+
84
+ it 'would normally not catch the error' do
85
+ expect { subject }.to raise_error(error)
86
+ end
87
+
88
+ context 'and errors option is set' do
89
+ let(:errors) { [ error ] }
90
+
91
+ it 'catches the error' do
92
+ expect { subject }.not_to raise_error
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'when there is an error during lock acquisition' do
100
+ before do
101
+ expect(limiter).to receive(:acquire_lock).and_raise(error)
102
+ end
103
+
104
+ it 'raises an exception for the caller' do
105
+ expect { limiter.limit }.to raise_error(error)
106
+ end
107
+
108
+ context 'when FailOpen middleware is enabled' do
109
+ before do
110
+ Berater.middleware.use described_class
111
+ end
112
+
113
+ it 'fails open' do
114
+ expect(limiter.limit).to be_a Berater::Lock
115
+ end
116
+
117
+ it 'returns the intended result' do
118
+ expect(limiter.limit { 123 }).to be 123
119
+ end
120
+ end
121
+
122
+ context 'when FailOpen middleware is enabled with callback' do
123
+ before do
124
+ Berater.middleware.use described_class, on_fail: on_fail
125
+ end
126
+ let(:on_fail) { double(Proc) }
127
+
128
+ it 'calls the callback' do
129
+ expect(on_fail).to receive(:call).with(Exception)
130
+ limiter.limit
131
+ end
132
+ end
133
+ end
134
+
135
+ context 'when there is an error during lock release' do
136
+ before do
137
+ allow(limiter).to receive(:acquire_lock).and_return(lock)
138
+ allow(lock).to receive(:release).and_raise(error)
139
+ end
140
+
141
+ it 'acquires a lock' do
142
+ expect(limiter.limit).to be_a Berater::Lock
143
+ expect(limiter.limit).to be lock
144
+ end
145
+
146
+ it 'raises an exception when lock is released' do
147
+ expect {
148
+ limiter.limit.release
149
+ }.to raise_error(error)
150
+ end
151
+
152
+ it 'raises an exception when lock is auto released' do
153
+ expect {
154
+ limiter.limit {}
155
+ }.to raise_error(error)
156
+ end
157
+
158
+ context 'when FailOpen middleware is enabled' do
159
+ before do
160
+ Berater.middleware.use described_class
161
+ end
162
+
163
+ it 'fails open' do
164
+ expect { limiter.limit.release }.not_to raise_error
165
+ end
166
+
167
+ it 'returns the intended result' do
168
+ expect(limiter.limit { 123 }).to be 123
169
+ end
170
+ end
171
+
172
+ context 'when FailOpen middleware is enabled with callback' do
173
+ before do
174
+ Berater.middleware.use described_class, on_fail: on_fail
175
+ end
176
+ let(:on_fail) { double(Proc) }
177
+
178
+ it 'calls the callback' do
179
+ expect(on_fail).to receive(:call).with(Exception)
180
+ limiter.limit {}
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,130 @@
1
+ describe Berater::Middleware::LoadShedder do
2
+ describe '#call' do
3
+ subject { described_class.new }
4
+
5
+ before { Berater.test_mode = :pass }
6
+
7
+ it 'yields' do
8
+ expect {|b| subject.call(&b) }.to yield_control
9
+ end
10
+
11
+ it 'passes through capacity and cost options' do
12
+ opts = {
13
+ capacity: 1,
14
+ cost: 2,
15
+ }
16
+
17
+ subject.call(**opts) do |**passed_opts|
18
+ expect(passed_opts).to eq(opts)
19
+ end
20
+ end
21
+
22
+ it 'strips out priority from options' do
23
+ opts = {
24
+ capacity: 1,
25
+ priority: 3,
26
+ }
27
+
28
+ subject.call(**opts) do |**passed_opts|
29
+ expect(passed_opts.keys).not_to include(:priority)
30
+ end
31
+ end
32
+
33
+ it 'keeps full capacity for priority 1' do
34
+ subject.call(capacity: 100, priority: 1) do |capacity:|
35
+ expect(capacity).to eq 100
36
+ end
37
+ end
38
+
39
+ it 'adjusts the capactiy according to priority' do
40
+ subject.call(capacity: 100, priority: 2) do |capacity:|
41
+ expect(capacity).to be < 100
42
+ end
43
+
44
+ subject.call(capacity: 100, priority: 5) do |capacity:|
45
+ expect(capacity).to eq 60
46
+ end
47
+ end
48
+
49
+ it 'ignores bogus priority options' do
50
+ subject.call(capacity: 100, priority: 50) do |capacity:|
51
+ expect(capacity).to eq 100
52
+ end
53
+
54
+ subject.call(capacity: 100, priority: 'abc') do |capacity:|
55
+ expect(capacity).to eq 100
56
+ end
57
+
58
+ subject.call(capacity: 100, priority: '123') do |capacity:|
59
+ expect(capacity).to eq 100
60
+ end
61
+ end
62
+
63
+ it 'works with a fractional priority' do
64
+ subject.call(capacity: 100, priority: 1.5) do |capacity:|
65
+ expect(capacity).to be < 100
66
+ end
67
+ end
68
+
69
+ context 'with a default priority' do
70
+ subject { described_class.new(default_priority: 5) }
71
+
72
+ it 'keeps full capacity for priority 1' do
73
+ subject.call(capacity: 100, priority: 1) do |capacity:|
74
+ expect(capacity).to eq 100
75
+ end
76
+ end
77
+
78
+ it 'uses the default priority' do
79
+ subject.call(capacity: 100) do |capacity:|
80
+ expect(capacity).to eq 60
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'with a limiter' do
87
+ before do
88
+ Berater.middleware.use Berater::Middleware::LoadShedder
89
+ end
90
+
91
+ shared_examples 'limiter load shedding' do |limiter|
92
+ it 'passes through the capactiy properly' do
93
+ expect(limiter).to receive(:inner_limit).with(
94
+ hash_including(capacity: 100)
95
+ ).and_call_original
96
+
97
+ limiter.limit
98
+ end
99
+
100
+ it 'scales the capactiy with priority' do
101
+ expect(limiter).to receive(:inner_limit).with(
102
+ hash_including(capacity: 60)
103
+ ).and_call_original
104
+
105
+ limiter.limit(priority: 5)
106
+ end
107
+
108
+ it 'overloads properly' do
109
+ 60.times { limiter.limit(priority: 5) }
110
+
111
+ expect {
112
+ limiter.limit(priority: 5)
113
+ }.to be_overloaded
114
+
115
+ expect {
116
+ limiter.limit(priority: 4)
117
+ }.not_to be_overloaded
118
+
119
+ 39.times { limiter.limit(priority: 1) }
120
+
121
+ expect {
122
+ limiter.limit(priority: 1)
123
+ }.to be_overloaded
124
+ end
125
+ end
126
+
127
+ include_examples 'limiter load shedding', Berater::ConcurrencyLimiter.new(:key, 100)
128
+ include_examples 'limiter load shedding', Berater::RateLimiter.new(:key, 100, :second)
129
+ end
130
+ end
@@ -1,5 +1,5 @@
1
1
  class Meddler
2
- def call(*)
2
+ def call(*args, **kwargs)
3
3
  yield
4
4
  end
5
5
  end
@@ -73,18 +73,18 @@ describe 'Berater.middleware' do
73
73
 
74
74
  context 'when middleware meddles' do
75
75
  it 'can change the capacity' do
76
- expect(middleware).to receive(:call) do |limiter, opts, &block|
76
+ expect(middleware).to receive(:call) do |limiter, **opts, &block|
77
77
  opts[:capacity] = 0
78
- block.call
78
+ block.call(limiter, **opts)
79
79
  end
80
80
 
81
81
  expect { limiter.limit }.to be_overloaded
82
82
  end
83
83
 
84
84
  it 'can change the cost' do
85
- expect(middleware).to receive(:call) do |limiter, opts, &block|
85
+ expect(middleware).to receive(:call) do |limiter, **opts, &block|
86
86
  opts[:cost] = 2
87
- block.call
87
+ block.call(limiter, **opts)
88
88
  end
89
89
 
90
90
  expect { limiter.limit }.to be_overloaded
@@ -93,8 +93,8 @@ describe 'Berater.middleware' do
93
93
  it 'can change the limiter' do
94
94
  other_limiter = Berater::Inhibitor.new
95
95
 
96
- expect(middleware).to receive(:call) do |limiter, opts, &block|
97
- block.call other_limiter, opts
96
+ expect(middleware).to receive(:call) do |limiter, **opts, &block|
97
+ block.call(other_limiter, **opts)
98
98
  end
99
99
  expect(other_limiter).to receive(:acquire_lock).and_call_original
100
100
 
@@ -105,6 +105,16 @@ describe 'Berater.middleware' do
105
105
  expect(middleware).to receive(:call)
106
106
  expect(limiter.limit).to be nil
107
107
  end
108
+
109
+ it 'can intercept the lock' do
110
+ expect(middleware).to receive(:call) do |&block|
111
+ lock = block.call
112
+ expect(lock).to be_a Berater::Lock
113
+ expect(lock.capacity).to eq limiter.capacity
114
+ end
115
+
116
+ limiter.limit
117
+ end
108
118
  end
109
119
  end
110
120
  end
@@ -19,12 +19,13 @@ describe Berater::RateLimiter do
19
19
  describe '#capacity' do
20
20
  def expect_capacity(capacity)
21
21
  limiter = described_class.new(:key, capacity, :second)
22
- expect(limiter.capacity).to eq capacity
22
+ expect(limiter.capacity).to eq capacity.to_f
23
23
  end
24
24
 
25
25
  it { expect_capacity(0) }
26
26
  it { expect_capacity(1) }
27
27
  it { expect_capacity(1.5) }
28
+ it { expect_capacity('1.5') }
28
29
  it { expect_capacity(100) }
29
30
 
30
31
  context 'with erroneous values' do
@@ -35,7 +36,7 @@ describe Berater::RateLimiter do
35
36
  end
36
37
 
37
38
  it { expect_bad_capacity(-1) }
38
- it { expect_bad_capacity('1') }
39
+ it { expect_bad_capacity('abc') }
39
40
  it { expect_bad_capacity(:one) }
40
41
  it { expect_bad_capacity(Float::INFINITY) }
41
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-09 00:00:00.000000000 Z
11
+ date: 2021-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: meddleware
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '0.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '0.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: redis
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: benchmark
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +150,9 @@ files:
150
150
  - lib/berater/limiter_set.rb
151
151
  - lib/berater/lock.rb
152
152
  - lib/berater/lua_script.rb
153
+ - lib/berater/middleware.rb
154
+ - lib/berater/middleware/fail_open.rb
155
+ - lib/berater/middleware/load_shedder.rb
153
156
  - lib/berater/rate_limiter.rb
154
157
  - lib/berater/rspec.rb
155
158
  - lib/berater/rspec/matchers.rb
@@ -167,6 +170,8 @@ files:
167
170
  - spec/limiter_spec.rb
168
171
  - spec/lua_script_spec.rb
169
172
  - spec/matchers_spec.rb
173
+ - spec/middleware/fail_open_spec.rb
174
+ - spec/middleware/load_shedder_spec.rb
170
175
  - spec/middleware_spec.rb
171
176
  - spec/rate_limiter_spec.rb
172
177
  - spec/riddle_spec.rb
@@ -193,12 +198,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
198
  - !ruby/object:Gem::Version
194
199
  version: '0'
195
200
  requirements: []
196
- rubygems_version: 3.0.8
201
+ rubygems_version: 3.1.6
197
202
  signing_key:
198
203
  specification_version: 4
199
204
  summary: Berater
200
205
  test_files:
201
206
  - spec/rate_limiter_spec.rb
207
+ - spec/middleware/load_shedder_spec.rb
208
+ - spec/middleware/fail_open_spec.rb
202
209
  - spec/matchers_spec.rb
203
210
  - spec/dsl_refinement_spec.rb
204
211
  - spec/test_mode_spec.rb