Skip to content

Commit 1e002a3

Browse files
committed
refactor: Clean up and simplify TokenOwnerResolver implementation
Remove dead code and improve error handling: - Remove fetch_endpoint_helpers: Endpoints don't have a public helpers method, making this code path unreachable - Simplify resolve_from_helper: Remove non-Module branch since helpers from namespace_stackable are always Modules - Simplify gather_helpers: Only one source of helpers (stackable), remove unnecessary array consolidation - Remove __send__ usage: inheritable_setting is a public method, use direct call - Improve exception handling: Replace broad rescue StandardError with specific NoMethodError and NameError catches in fetch_stackable_helpers Error handling improvements: - Add GrapeSwagger::Errors::TokenOwnerNotFound inheriting from NoMethodError for more semantic error hierarchy - Update resolver to raise custom error instead of generic NoMethodError, allowing users to catch grape-swagger-specific issues
1 parent 66a7d51 commit 1e002a3

File tree

3 files changed

+80
-41
lines changed

3 files changed

+80
-41
lines changed

lib/grape-swagger/errors.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ def tell!(what)
1313
end
1414
end
1515
end
16+
17+
class TokenOwnerNotFound < NoMethodError; end
1618
end
1719
end

lib/grape-swagger/token_owner_resolver.rb

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def resolve(endpoint, method_name)
1616
helper_value = resolve_from_helpers(endpoint, method_name)
1717
return helper_value unless helper_value.equal?(UNRESOLVED)
1818

19-
raise NoMethodError, "undefined method `#{method_name}` for #{endpoint.inspect}"
19+
raise Errors::TokenOwnerNotFound, "undefined method `#{method_name}` for #{endpoint.inspect}"
2020
end
2121

2222
def evaluate_proc(callable, token_owner)
@@ -27,12 +27,6 @@ def evaluate_proc(callable, token_owner)
2727

2828
private
2929

30-
def accepts_argument?(callable)
31-
return false unless callable.respond_to?(:parameters)
32-
33-
callable.parameters.any? { |type, _| SUPPORTED_ARITY_TYPES.include?(type) }
34-
end
35-
3630
def resolve_from_helpers(endpoint, method_name)
3731
helpers = gather_helpers(endpoint)
3832
return UNRESOLVED if helpers.empty?
@@ -48,30 +42,22 @@ def resolve_from_helpers(endpoint, method_name)
4842
def gather_helpers(endpoint)
4943
return [] if endpoint.nil?
5044

51-
helpers = []
52-
endpoint_helpers = fetch_endpoint_helpers(endpoint)
53-
helpers.concat(normalize_helpers(endpoint_helpers)) if endpoint_helpers
54-
5545
stackable_helpers = fetch_stackable_helpers(endpoint)
56-
helpers.concat(normalize_helpers(stackable_helpers)) if stackable_helpers
57-
58-
helpers.compact.uniq
46+
normalize_helpers(stackable_helpers)
5947
end
6048

61-
def resolve_from_helper(endpoint, helper, method_name)
62-
if helper.is_a?(Module)
63-
return UNRESOLVED unless helper_method_defined?(helper, method_name)
49+
def fetch_stackable_helpers(endpoint)
50+
return unless endpoint.respond_to?(:inheritable_setting)
6451

65-
return helper.instance_method(method_name).bind(endpoint).call
66-
end
52+
setting = endpoint.inheritable_setting
53+
return unless setting.respond_to?(:namespace_stackable)
6754

68-
helper.respond_to?(method_name, true) ? helper.public_send(method_name) : UNRESOLVED
69-
rescue NameError
70-
UNRESOLVED
71-
end
55+
namespace_stackable = setting.namespace_stackable
56+
return unless namespace_stackable.respond_to?(:[])
7257

73-
def helper_method_defined?(helper, method_name)
74-
helper.method_defined?(method_name) || helper.private_method_defined?(method_name)
58+
namespace_stackable[:helpers]
59+
rescue NameError
60+
nil
7561
end
7662

7763
def normalize_helpers(helpers)
@@ -93,26 +79,22 @@ def normalize_helpers(helpers)
9379
end
9480
end
9581

96-
def fetch_endpoint_helpers(endpoint)
97-
return unless endpoint.respond_to?(:helpers, true)
82+
def resolve_from_helper(endpoint, helper, method_name)
83+
return UNRESOLVED unless helper_method_defined?(helper, method_name)
9884

99-
endpoint.__send__(:helpers)
100-
rescue StandardError
101-
nil
85+
helper.instance_method(method_name).bind(endpoint).call
86+
rescue NameError
87+
UNRESOLVED
10288
end
10389

104-
def fetch_stackable_helpers(endpoint)
105-
return unless endpoint.respond_to?(:inheritable_setting, true)
106-
107-
setting = endpoint.__send__(:inheritable_setting)
108-
return unless setting.respond_to?(:namespace_stackable)
90+
def helper_method_defined?(helper, method_name)
91+
helper.method_defined?(method_name) || helper.private_method_defined?(method_name)
92+
end
10993

110-
namespace_stackable = setting.namespace_stackable
111-
return unless namespace_stackable.respond_to?(:[])
94+
def accepts_argument?(callable)
95+
return false unless callable.respond_to?(:parameters)
11296

113-
namespace_stackable[:helpers]
114-
rescue StandardError
115-
nil
97+
callable.parameters.any? { |type, _| SUPPORTED_ARITY_TYPES.include?(type) }
11698
end
11799
end
118100
end

spec/lib/token_owner_resolver_spec.rb

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def token_owner
4242
it 'raises when the endpoint does not respond to the method' do
4343
expect do
4444
expect(described_class.resolve(endpoint, :unknown))
45-
end.to raise_error(NoMethodError, /undefined method `unknown`/)
45+
end.to raise_error(GrapeSwagger::Errors::TokenOwnerNotFound, /undefined method `unknown`/)
4646
end
4747

4848
context 'when helpers are included from a module' do
@@ -80,4 +80,59 @@ def call(owner = :undetected)
8080
expect(described_class.evaluate_proc(callable, token_owner)).to eq(:undetected)
8181
end
8282
end
83+
84+
describe '.resolve_from_helper' do
85+
let(:helper_module) do
86+
Module.new do
87+
def helper_method
88+
'helper_result'
89+
end
90+
end
91+
end
92+
93+
let(:endpoint) { instance_double(Grape::Endpoint) }
94+
95+
it 'resolves the method from the Module helper' do
96+
result = described_class.send(:resolve_from_helper, endpoint, helper_module, :helper_method)
97+
expect(result).to eq('helper_result')
98+
end
99+
100+
it 'returns a frozen sentinel object when method does not exist on the Module' do
101+
result = described_class.send(:resolve_from_helper, endpoint, helper_module, :nonexistent)
102+
# UNRESOLVED is a private constant, so we check by type and behavior
103+
expect(result).to be_a(Object)
104+
expect(result).to be_frozen
105+
# Verify it's the same object on repeated calls (singleton pattern)
106+
result2 = described_class.send(:resolve_from_helper, endpoint, helper_module, :nonexistent)
107+
expect(result).to equal(result2)
108+
end
109+
end
110+
111+
describe 'endpoint helpers access' do
112+
let(:helper_module) do
113+
Module.new do
114+
def current_user
115+
{ id: 42, name: 'Test User' }
116+
end
117+
end
118+
end
119+
120+
let(:api_class) do
121+
mod = helper_module
122+
Class.new(Grape::API) do
123+
helpers mod
124+
125+
get('/test') { { ok: true } }
126+
end
127+
end
128+
129+
before { api_class.compile! }
130+
131+
let(:endpoint) { api_class.endpoints.first }
132+
133+
it 'resolves helper methods from namespace stack' do
134+
resolved_value = described_class.resolve(endpoint, :current_user)
135+
expect(resolved_value).to eq(id: 42, name: 'Test User')
136+
end
137+
end
83138
end

0 commit comments

Comments
 (0)