Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

* [#2538](https://github.com/ruby-grape/grape/pull/2538): Fix validating nested json array params - [@mohammednasser-32](https://github.com/mohammednasser-32).
* [#2543](https://github.com/ruby-grape/grape/pull/2543): Fix array allocation on mount - [@ericproulx](https://github.com/ericproulx).
* [#2546](https://github.com/ruby-grape/grape/pull/2546): Fix middleware with keywords - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

### 2.3.0 (2025-02-08)
Expand Down
47 changes: 24 additions & 23 deletions lib/grape/middleware/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,16 @@ module Middleware
class Stack
extend Forwardable
class Middleware
extend Forwardable

attr_reader :args, :block, :klass

def_delegators :klass, :name

def initialize(klass, *args, &block)
def initialize(klass, args, block)
@klass = klass
@args = args
@block = block
end

def name; klass.name; end # rubocop:disable Style/SingleLineMethods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems silly to stick it to 1 line and disable a rubocop rule on it.


def ==(other)
case other
when Middleware
Expand All @@ -32,7 +30,11 @@ def inspect
klass.to_s
end

def use_in(builder)
def build(builder)
# we need to force the ruby2_keywords_hash for middlewares that initialize contains keywords
# like ActionDispatch::RequestId since middleware arguments are serialized
# https://rubyapi.org/3.4/o/hash#method-c-ruby2_keywords_hash
args[-1] = Hash.ruby2_keywords_hash(args[-1]) if args.last.is_a?(Hash) && Hash.respond_to?(:ruby2_keywords_hash)
builder.use(klass, *args, &block)
end
end
Expand All @@ -48,51 +50,50 @@ def initialize
@others = []
end

def insert(index, *args, &block)
def insert(index, klass, *args, &block)
index = assert_index(index, :before)
middleware = self.class::Middleware.new(*args, &block)
middlewares.insert(index, middleware)
middlewares.insert(index, self.class::Middleware.new(klass, args, block))
end
ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)

alias insert_before insert

def insert_after(index, *args, &block)
index = assert_index(index, :after)
insert(index + 1, *args, &block)
end
ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)

def use(...)
middleware = self.class::Middleware.new(...)
def use(klass, *args, &block)
middleware = self.class::Middleware.new(klass, args, block)
middlewares.push(middleware)
end

def merge_with(middleware_specs)
middleware_specs.each do |operation, *args|
middleware_specs.each do |operation, klass, *args|
if args.last.is_a?(Proc)
last_proc = args.pop
public_send(operation, *args, &last_proc)
public_send(operation, klass, *args, &last_proc)
else
public_send(operation, *args)
public_send(operation, klass, *args)
end
end
end

# @return [Rack::Builder] the builder object with our middlewares applied
def build(builder = Rack::Builder.new)
others.shift(others.size).each { |m| merge_with(m) }
middlewares.each do |m|
m.use_in(builder)
def build
Rack::Builder.new.tap do |builder|
others.shift(others.size).each { |m| merge_with(m) }
middlewares.each do |m|
m.build(builder)
end
end
builder
end

# @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
# @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
def concat(other_specs)
@others << Array(other_specs).reject { |o| o.first == :use }
merge_with(Array(other_specs).select { |o| o.first == :use })
use, not_use = other_specs.partition { |o| o.first == :use }
others << not_use
merge_with(use)
end

protected
Expand Down
28 changes: 28 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,34 @@ def before
expect(last_response).to be_bad_request
expect(last_response.body).to eq('Caught in the Net')
end

context 'when middleware initialize as keywords' do
let(:middleware_with_keywords) do
Class.new do
def initialize(app, keyword:)
@app = app
@keyword = keyword
end

def call(env)
env['middleware_with_keywords'] = @keyword
@app.call(env)
end
end
end

before do
subject.use middleware_with_keywords, keyword: 'hello'
subject.get '/' do
env['middleware_with_keywords']
end
get '/'
end

it 'returns the middleware value' do
expect(last_response.body).to eq('hello')
end
end
end

describe '.insert_before' do
Expand Down