Skip to content

Commit 4a1983e

Browse files
authored
Add Grape 3 compatibility (#966)
1 parent 232baa5 commit 4a1983e

File tree

11 files changed

+279
-29
lines changed

11 files changed

+279
-29
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,9 @@ jobs:
4343
- { ruby: '3.3', grape: '2.2.0' }
4444
- { ruby: '3.4', grape: '2.2.0' }
4545
- { ruby: 'head', grape: '2.2.0' }
46-
# - { ruby: '3.1', grape: 'HEAD' }
47-
# - { ruby: '3.2', grape: 'HEAD' }
48-
# - { ruby: '3.3', grape: 'HEAD' }
49-
# - { ruby: '3.4', grape: 'HEAD' }
46+
- { ruby: '3.2', grape: 'HEAD' }
47+
- { ruby: '3.3', grape: 'HEAD' }
48+
- { ruby: '3.4', grape: 'HEAD' }
5049
name: test (ruby=${{ matrix.entry.ruby }}, grape=${{ matrix.entry.grape }})
5150
runs-on: ubuntu-latest
5251
needs: ['rubocop']

CHANGELOG.md

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

1212
#### Fixes
1313

14+
* [#966](https://github.com/ruby-grape/grape-swagger/pull/966): Grape 3.0 compatibility - [@numbata](https://github.com/numbata).
1415
* [#954](https://github.com/ruby-grape/grape-swagger/pull/954): Ruby 3.5 compatibility: add cgi gem, drop runtime `rack‑test` - [@numbata](https://github.com/numbata).
1516
* [#948](https://github.com/ruby-grape/grape-swagger/pull/948): Grape 2.3.0 and Ruby 3.5 compatibility - [@numbata](https://github.com/numbata).
1617
* Your contribution here.

Gemfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
55
gemspec
66

77
# gem 'grape', git: 'https://github.com/ruby-grape/grape'
8-
gem 'grape', case version = ENV.fetch('GRAPE_VERSION', '< 3.0')
8+
gem 'grape', case version = ENV.fetch('GRAPE_VERSION', '< 4.0')
99
when 'HEAD'
1010
{ git: 'https://github.com/ruby-grape/grape' }
1111
else
@@ -20,10 +20,11 @@ group :development, :test do
2020
gem 'pry', platforms: [:mri]
2121
gem 'pry-byebug', platforms: [:mri]
2222

23-
grape_version = ENV.fetch('GRAPE_VERSION', '2.2.0')
23+
grape_version = ENV.fetch('GRAPE_VERSION', '2.4.0')
2424
if grape_version == 'HEAD' || Gem::Version.new(grape_version) >= Gem::Version.new('2.0.0')
2525
gem 'rack', '>= 3.0'
2626
else
27+
gem 'activesupport', '< 7.2'
2728
gem 'rack', '< 3.0'
2829
end
2930

README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,19 @@ This screenshot is based on the [Hussars](https://github.com/LeFnord/hussars) sa
113113

114114
The following versions of grape, grape-entity and grape-swagger can currently be used together.
115115

116-
| grape-swagger | swagger spec | grape | grape-entity | representable |
117-
| ------------------ | ------------ | ----------------------- | ------------ | ------------- |
118-
| 0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
119-
| 0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
120-
| 0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
121-
| 0.26.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
122-
| 0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
123-
| 0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
124-
| 0.34.0 | 2.0 | >= 0.16.2 ... < 1.3.0 | >= 0.5.0 | >= 2.4.1 |
125-
| >= 1.0.0 | 2.0 | >= 1.3.0 | >= 0.5.0 | >= 2.4.1 |
126-
| >= 2.0.0 | 2.0 | >= 1.7.0 | >= 0.5.0 | >= 2.4.1 |
127-
| >= 2.0.0 ... < 2.2 | 2.0 | >= 1.8.0 ... < 2.3.0 | >= 0.5.0 | >= 2.4.1 |
116+
| grape-swagger | swagger spec | grape | grape-entity | representable |
117+
| --------------------- | ------------ | ----------------------- | ------------ | ------------- |
118+
| 0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
119+
| 0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
120+
| 0.25.2 | 2.0 | >= 0.14.0 ... <= 0.18.0 | <= 0.6.0 | >= 2.4.1 |
121+
| 0.26.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | <= 0.6.1 | >= 2.4.1 |
122+
| 0.27.0 | 2.0 | >= 0.16.2 ... <= 1.1.0 | >= 0.5.0 | >= 2.4.1 |
123+
| 0.32.0 | 2.0 | >= 0.16.2 | >= 0.5.0 | >= 2.4.1 |
124+
| 0.34.0 | 2.0 | >= 0.16.2 ... < 1.3.0 | >= 0.5.0 | >= 2.4.1 |
125+
| >= 1.0.0 | 2.0 | >= 1.3.0 | >= 0.5.0 | >= 2.4.1 |
126+
| >= 2.0.0 | 2.0 | >= 1.7.0 | >= 0.5.0 | >= 2.4.1 |
127+
| >= 2.0.0 ... <= 2.1.2 | 2.0 | >= 1.8.0 ... < 2.3.0 | >= 0.5.0 | >= 2.4.1 |
128+
| > 2.1.2 | 2.0 | >= 1.8.0 ... < 4.0 | >= 0.5.0 | >= 2.4.1 |
128129

129130

130131
## Swagger-Spec <a name="swagger-spec"></a>

grape-swagger.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
1515
s.metadata['rubygems_mfa_required'] = 'true'
1616

1717
s.required_ruby_version = '>= 3.1'
18-
s.add_dependency 'grape', '>= 1.7', '< 3.0'
18+
s.add_dependency 'grape', '>= 1.7', '< 4.0'
1919

2020
s.files = Dir['lib/**/*', '*.md', 'LICENSE.txt', 'grape-swagger.gemspec']
2121
s.require_paths = ['lib']

lib/grape-swagger.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
require 'grape-swagger/doc_methods'
1212
require 'grape-swagger/model_parsers'
1313
require 'grape-swagger/request_param_parser_registry'
14+
require 'grape-swagger/token_owner_resolver'
1415

1516
module GrapeSwagger
1617
class << self
@@ -184,12 +185,13 @@ def combine_namespaces(app)
184185
endpoint = endpoints.shift
185186

186187
endpoints.push(*endpoint.options[:app].endpoints) if endpoint.options[:app]
187-
ns = endpoint.namespace_stackable(:namespace).last
188+
namespace_stackable = endpoint.inheritable_setting.namespace_stackable
189+
ns = (namespace_stackable[:namespace] || []).last
188190
next unless ns
189191

190192
# use the full namespace here (not the latest level only)
191193
# and strip leading slash
192-
mount_path = (endpoint.namespace_stackable(:mount_path) || []).join('/')
194+
mount_path = (namespace_stackable[:mount_path] || []).join('/')
193195
full_namespace = (mount_path + endpoint.namespace).sub(/\/{2,}/, '/').sub(/^\//, '')
194196
combined_namespaces[full_namespace] = ns
195197
end

lib/grape-swagger/endpoint.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require_relative 'request_param_parsers/headers'
66
require_relative 'request_param_parsers/route'
77
require_relative 'request_param_parsers/body'
8+
require_relative 'token_owner_resolver'
89

910
module Grape
1011
class Endpoint # rubocop:disable Metrics/ClassLength
@@ -439,7 +440,10 @@ def hidden?(route, options)
439440
route_hidden = route.options[:hidden] if route.options.key?(:hidden)
440441
return route_hidden unless route_hidden.is_a?(Proc)
441442

442-
options[:token_owner] ? route_hidden.call(send(options[:token_owner].to_sym)) : route_hidden.call
443+
return route_hidden.call unless options[:token_owner]
444+
445+
token_owner = GrapeSwagger::TokenOwnerResolver.resolve(self, options[:token_owner])
446+
GrapeSwagger::TokenOwnerResolver.evaluate_proc(route_hidden, token_owner)
443447
end
444448

445449
def hidden_parameter?(value)

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/request_param_parsers/body.rb

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,7 @@ def public_parameter?(param_options)
5454
return true unless param_options.key?(:documentation) && !param_options[:required]
5555

5656
param_hidden = param_options[:documentation].fetch(:hidden, false)
57-
if param_hidden.is_a?(Proc)
58-
param_hidden = if settings[:token_owner]
59-
param_hidden.call(endpoint.send(settings[:token_owner].to_sym))
60-
else
61-
param_hidden.call
62-
end
63-
end
57+
param_hidden = evaluate_hidden_proc(param_hidden) if param_hidden.is_a?(Proc)
6458
!param_hidden
6559
end
6660

@@ -69,6 +63,13 @@ def includes_body_param?
6963
options.dig(:documentation, :param_type) == 'body' || options.dig(:documentation, :in) == 'body'
7064
end
7165
end
66+
67+
def evaluate_hidden_proc(hidden_proc)
68+
return hidden_proc.call unless settings[:token_owner]
69+
70+
token_owner = GrapeSwagger::TokenOwnerResolver.resolve(endpoint, settings[:token_owner])
71+
GrapeSwagger::TokenOwnerResolver.evaluate_proc(hidden_proc, token_owner)
72+
end
7273
end
7374
end
7475
end
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# frozen_string_literal: true
2+
3+
module GrapeSwagger
4+
class TokenOwnerResolver
5+
class << self
6+
SUPPORTED_ARITY_TYPES = %i[req opt rest keyreq key keyrest].freeze
7+
UNRESOLVED = Object.new.freeze
8+
private_constant :UNRESOLVED
9+
10+
def resolve(endpoint, method_name)
11+
return if method_name.nil?
12+
13+
method_name = method_name.to_sym
14+
return endpoint.public_send(method_name) if endpoint.respond_to?(method_name, true)
15+
16+
helper_value = resolve_from_helpers(endpoint, method_name)
17+
return helper_value unless helper_value.equal?(UNRESOLVED)
18+
19+
raise Errors::TokenOwnerNotFound, "undefined method `#{method_name}` for #{endpoint.class}"
20+
end
21+
22+
def evaluate_proc(callable, token_owner)
23+
return callable.call unless accepts_argument?(callable)
24+
25+
callable.call(token_owner)
26+
end
27+
28+
private
29+
30+
def resolve_from_helpers(endpoint, method_name)
31+
helpers = gather_helpers(endpoint)
32+
return UNRESOLVED if helpers.empty?
33+
34+
helpers.each do |helper|
35+
resolved = resolve_from_helper(endpoint, helper, method_name)
36+
return resolved unless resolved.equal?(UNRESOLVED)
37+
end
38+
39+
UNRESOLVED
40+
end
41+
42+
def gather_helpers(endpoint)
43+
return [] if endpoint.nil?
44+
45+
stackable_helpers = fetch_stackable_helpers(endpoint)
46+
normalize_helpers(stackable_helpers)
47+
end
48+
49+
def fetch_stackable_helpers(endpoint)
50+
return unless endpoint.respond_to?(:inheritable_setting)
51+
52+
setting = endpoint.inheritable_setting
53+
return unless setting.respond_to?(:namespace_stackable)
54+
55+
namespace_stackable = setting.namespace_stackable
56+
return unless namespace_stackable.respond_to?(:[])
57+
58+
namespace_stackable[:helpers]
59+
rescue NameError
60+
nil
61+
end
62+
63+
def normalize_helpers(helpers)
64+
case helpers
65+
when nil, false
66+
[]
67+
when Module
68+
[helpers]
69+
when Array
70+
helpers.compact
71+
else
72+
if helpers.respond_to?(:key?) && helpers.respond_to?(:[]) && helpers.key?(:helpers)
73+
normalize_helpers(helpers[:helpers])
74+
elsif helpers.respond_to?(:to_a)
75+
Array(helpers.to_a).flatten.compact
76+
else
77+
Array(helpers).compact
78+
end
79+
end
80+
end
81+
82+
def resolve_from_helper(endpoint, helper, method_name)
83+
return UNRESOLVED unless helper_method_defined?(helper, method_name)
84+
85+
helper.instance_method(method_name).bind(endpoint).call
86+
rescue NameError
87+
UNRESOLVED
88+
end
89+
90+
def helper_method_defined?(helper, method_name)
91+
helper.method_defined?(method_name) || helper.private_method_defined?(method_name)
92+
end
93+
94+
def accepts_argument?(callable)
95+
return false unless callable.respond_to?(:parameters)
96+
97+
callable.parameters.any? { |type, _| SUPPORTED_ARITY_TYPES.include?(type) }
98+
end
99+
end
100+
end
101+
end

0 commit comments

Comments
 (0)