Skip to content

Commit 24024d2

Browse files
author
Ryan Buckley
committed
Middleware can be inserted before or after default middlewares
1 parent 05bb1b4 commit 24024d2

File tree

4 files changed

+83
-43
lines changed

4 files changed

+83
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#### Features
77

8+
* [#1393](https://github.com/ruby-grape/grape/pull/1393): Middleware can be inserted before or after default Grape middleware - [@ridiculous](https://github.com/ridiculous).
89
* [#1390](https://github.com/ruby-grape/grape/pull/1390): Allow inserting middleware at arbitrary points in the middleware stack - [@Rosa](https://github.com/Rosa).
910
* [#1366](https://github.com/ruby-grape/grape/pull/1366): Store `message_key` on `Grape::Exceptions::Validation` - [@mkou](https://github.com/mkou).
1011

lib/grape/endpoint.rb

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -245,42 +245,39 @@ def run
245245
end
246246

247247
def build_stack
248-
ms = Grape::Middleware::Stack.new
249-
250-
ms.use Rack::Head
251-
ms.use Grape::Middleware::Error,
252-
format: namespace_inheritable(:format),
253-
content_types: namespace_stackable_with_hash(:content_types),
254-
default_status: namespace_inheritable(:default_error_status),
255-
rescue_all: namespace_inheritable(:rescue_all),
256-
default_error_formatter: namespace_inheritable(:default_error_formatter),
257-
error_formatters: namespace_stackable_with_hash(:error_formatters),
258-
rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
259-
rescue_handlers: namespace_stackable_with_hash(:rescue_handlers) || {},
260-
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
261-
all_rescue_handler: namespace_inheritable(:all_rescue_handler)
262-
263-
ms.merge_with(namespace_stackable(:middleware) || [])
248+
stack = Grape::Middleware::Stack.new
249+
250+
stack.use Rack::Head
251+
stack.use Grape::Middleware::Error,
252+
format: namespace_inheritable(:format),
253+
content_types: namespace_stackable_with_hash(:content_types),
254+
default_status: namespace_inheritable(:default_error_status),
255+
rescue_all: namespace_inheritable(:rescue_all),
256+
default_error_formatter: namespace_inheritable(:default_error_formatter),
257+
error_formatters: namespace_stackable_with_hash(:error_formatters),
258+
rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
259+
rescue_handlers: namespace_stackable_with_hash(:rescue_handlers) || {},
260+
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
261+
all_rescue_handler: namespace_inheritable(:all_rescue_handler)
262+
263+
stack.concat namespace_stackable(:middleware)
264264

265265
if namespace_inheritable(:version)
266-
ms.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
267-
versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil,
268-
version_options: namespace_inheritable(:version_options),
269-
prefix: namespace_inheritable(:root_prefix)
266+
stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
267+
versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil,
268+
version_options: namespace_inheritable(:version_options),
269+
prefix: namespace_inheritable(:root_prefix)
270270
end
271271

272-
ms.use Grape::Middleware::Formatter,
273-
format: namespace_inheritable(:format),
274-
default_format: namespace_inheritable(:default_format) || :txt,
275-
content_types: namespace_stackable_with_hash(:content_types),
276-
formatters: namespace_stackable_with_hash(:formatters),
277-
parsers: namespace_stackable_with_hash(:parsers)
278-
279-
builder = Rack::Builder.new
280-
ms.build(builder)
272+
stack.use Grape::Middleware::Formatter,
273+
format: namespace_inheritable(:format),
274+
default_format: namespace_inheritable(:default_format) || :txt,
275+
content_types: namespace_stackable_with_hash(:content_types),
276+
formatters: namespace_stackable_with_hash(:formatters),
277+
parsers: namespace_stackable_with_hash(:parsers)
281278

279+
builder = stack.build
282280
builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
283-
284281
builder.to_app
285282
end
286283

lib/grape/middleware/stack.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ def inspect
3232

3333
include Enumerable
3434

35-
attr_accessor :middlewares
35+
attr_accessor :middlewares, :others
3636

3737
def initialize
3838
@middlewares = []
39+
@others = []
3940
end
4041

4142
def each
@@ -72,25 +73,37 @@ def use(*args, &block)
7273
middlewares.push(middleware)
7374
end
7475

75-
def merge_with(other)
76-
other.each do |operation, *args|
77-
block = args.pop if args.last.is_a?(Proc)
78-
block ? send(operation, *args, &block) : send(operation, *args)
76+
def merge_with(middleware_specs)
77+
middleware_specs.each do |operation, *args|
78+
if args.last.is_a?(Proc)
79+
public_send(operation, *args, &args.pop)
80+
else
81+
public_send(operation, *args)
82+
end
7983
end
8084
end
8185

82-
def build(builder)
86+
# @return [Rack::Builder] the builder object with our middlewares applied
87+
def build(builder = Rack::Builder.new)
88+
others.shift(others.size).each(&method(:merge_with))
8389
middlewares.each do |m|
8490
m.block ? builder.use(m.klass, *m.args, &m.block) : builder.use(m.klass, *m.args)
8591
end
92+
builder
93+
end
94+
95+
# @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
96+
# @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
97+
def concat(other_specs)
98+
@others << Array(other_specs).reject { |o| o.first == :use }
99+
merge_with Array(other_specs).select { |o| o.first == :use }
86100
end
87101

88102
protected
89103

90104
def assert_index(index, where)
91105
i = index.is_a?(Integer) ? index : middlewares.index(index)
92-
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
93-
i
106+
i || raise("No such middleware to insert #{where}: #{index.inspect}")
94107
end
95108
end
96109
end

spec/grape/middleware/stack_spec.rb

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ def initialize(&block)
1212
end
1313
end
1414

15+
let(:proc) { ->() {} }
16+
let(:others) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
17+
1518
subject { Grape::Middleware::Stack.new }
1619

1720
before do
@@ -33,7 +36,6 @@ def initialize(&block)
3336
end
3437

3538
it 'pushes a middleware class with block arguments onto the stack' do
36-
proc = ->() {}
3739
expect { subject.use StackSpec::BlockMiddleware, &proc }
3840
.to change { subject.size }.by(1)
3941
expect(subject.last).to eq(StackSpec::BlockMiddleware)
@@ -80,15 +82,42 @@ def initialize(&block)
8082
end
8183

8284
describe '#merge_with' do
83-
let(:proc) { ->() {} }
84-
let(:other) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
85-
8685
it 'applies a collection of operations and middlewares' do
87-
expect { subject.merge_with(other) }
86+
expect { subject.merge_with(others) }
8887
.to change { subject.size }.by(2)
8988
expect(subject[0]).to eq(StackSpec::FooMiddleware)
9089
expect(subject[1]).to eq(StackSpec::BlockMiddleware)
9190
expect(subject[2]).to eq(StackSpec::BarMiddleware)
9291
end
9392
end
93+
94+
describe '#build' do
95+
it 'returns a rack builder instance' do
96+
expect(subject.build).to be_instance_of(Rack::Builder)
97+
end
98+
99+
context 'when @others are present' do
100+
let(:others) { [[:insert_after, Grape::Middleware::Formatter, StackSpec::BarMiddleware]] }
101+
102+
it 'applies the middleware specs stored in @others' do
103+
subject.concat others
104+
subject.use Grape::Middleware::Formatter
105+
subject.build
106+
expect(subject[0]).to eq StackSpec::FooMiddleware
107+
expect(subject[1]).to eq Grape::Middleware::Formatter
108+
expect(subject[2]).to eq StackSpec::BarMiddleware
109+
end
110+
end
111+
end
112+
113+
describe '#concat' do
114+
it 'adds non :use specs to @others' do
115+
expect { subject.concat others }.to change(subject, :others).from([]).to([[others.last]])
116+
end
117+
118+
it 'calls +merge_with+ with the :use specs' do
119+
expect(subject).to receive(:merge_with).with [[:use, StackSpec::BarMiddleware]]
120+
subject.concat others
121+
end
122+
end
94123
end

0 commit comments

Comments
 (0)