berater 0.9.0 → 0.11.1

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 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