grape 1.2.3 → 1.2.4
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 +4 -4
- data/CHANGELOG.md +14 -1
- data/Gemfile.lock +12 -12
- data/LICENSE +1 -1
- data/README.md +103 -23
- data/UPGRADING.md +32 -0
- data/lib/grape.rb +2 -0
- data/lib/grape/api.rb +36 -9
- data/lib/grape/api/instance.rb +25 -2
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/desc.rb +6 -2
- data/lib/grape/dsl/inside_route.rb +12 -6
- data/lib/grape/endpoint.rb +36 -28
- data/lib/grape/middleware/error.rb +1 -2
- data/lib/grape/util/endpoint_configuration.rb +6 -0
- data/lib/grape/util/lazy_value.rb +90 -0
- data/lib/grape/validations/params_scope.rb +7 -2
- data/lib/grape/validations/validators/as.rb +2 -2
- data/lib/grape/version.rb +1 -1
- data/pkg/grape-1.2.3.gem +0 -0
- data/spec/grape/api_remount_spec.rb +235 -12
- data/spec/grape/api_spec.rb +210 -0
- data/spec/grape/endpoint_spec.rb +39 -3
- data/spec/grape/validations/params_scope_spec.rb +32 -12
- metadata +5 -3
- data/gemfiles/rails_edge.gemfile.lock +0 -335
@@ -36,6 +36,10 @@ module Grape
|
|
36
36
|
configure_declared_params
|
37
37
|
end
|
38
38
|
|
39
|
+
def configuration
|
40
|
+
@api.configuration.evaluate
|
41
|
+
end
|
42
|
+
|
39
43
|
# @return [Boolean] whether or not this entire scope needs to be
|
40
44
|
# validated
|
41
45
|
def should_validate?(parameters)
|
@@ -52,6 +56,7 @@ module Grape
|
|
52
56
|
|
53
57
|
return true unless @dependent_on
|
54
58
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
59
|
+
return false unless params.respond_to?(:with_indifferent_access)
|
55
60
|
params = params.with_indifferent_access
|
56
61
|
|
57
62
|
@dependent_on.each do |dependency|
|
@@ -119,8 +124,8 @@ module Grape
|
|
119
124
|
@parent.push_declared_params(attrs, opts)
|
120
125
|
else
|
121
126
|
if opts && opts[:as]
|
122
|
-
@api.route_setting(:
|
123
|
-
@api.route_setting(:
|
127
|
+
@api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
|
128
|
+
@api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
|
124
129
|
attrs = [opts[:as]]
|
125
130
|
end
|
126
131
|
|
@@ -2,12 +2,12 @@ module Grape
|
|
2
2
|
module Validations
|
3
3
|
class AsValidator < Base
|
4
4
|
def initialize(attrs, options, required, scope, opts = {})
|
5
|
-
@
|
5
|
+
@renamed_options = options
|
6
6
|
super
|
7
7
|
end
|
8
8
|
|
9
9
|
def validate_param!(attr_name, params)
|
10
|
-
params[@
|
10
|
+
params[@renamed_options] = params[attr_name]
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/grape/version.rb
CHANGED
data/pkg/grape-1.2.3.gem
ADDED
Binary file
|
@@ -63,22 +63,245 @@ describe Grape::API do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
a_remounted_api
|
69
|
-
|
70
|
-
'
|
66
|
+
describe 'with dynamic configuration' do
|
67
|
+
context 'when mounting an endpoint conditional on a configuration' do
|
68
|
+
subject(:a_remounted_api) do
|
69
|
+
Class.new(Grape::API) do
|
70
|
+
get 'always' do
|
71
|
+
'success'
|
72
|
+
end
|
73
|
+
|
74
|
+
given configuration[:mount_sometimes] do
|
75
|
+
get 'sometimes' do
|
76
|
+
'sometimes'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'mounts the endpoints only when configured to do so' do
|
83
|
+
root_api.mount({ a_remounted_api => 'with_conditional' }, with: { mount_sometimes: true })
|
84
|
+
root_api.mount({ a_remounted_api => 'without_conditional' }, with: { mount_sometimes: false })
|
85
|
+
|
86
|
+
get '/with_conditional/always'
|
87
|
+
expect(last_response.body).to eq 'success'
|
88
|
+
|
89
|
+
get '/with_conditional/sometimes'
|
90
|
+
expect(last_response.body).to eq 'sometimes'
|
91
|
+
|
92
|
+
get '/without_conditional/always'
|
93
|
+
expect(last_response.body).to eq 'success'
|
94
|
+
|
95
|
+
get '/without_conditional/sometimes'
|
96
|
+
expect(last_response.status).to eq 404
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when executing a custom block on mount' do
|
101
|
+
subject(:a_remounted_api) do
|
102
|
+
Class.new(Grape::API) do
|
103
|
+
get 'always' do
|
104
|
+
'success'
|
105
|
+
end
|
106
|
+
|
107
|
+
mounted do
|
108
|
+
configuration[:endpoints].each do |endpoint_name, endpoint_response|
|
109
|
+
get endpoint_name do
|
110
|
+
endpoint_response
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
71
114
|
end
|
72
115
|
end
|
73
|
-
|
74
|
-
|
116
|
+
|
117
|
+
it 'mounts the endpoints only when configured to do so' do
|
118
|
+
root_api.mount a_remounted_api, with: { endpoints: { 'api_name' => 'api_response' } }
|
119
|
+
get 'api_name'
|
120
|
+
expect(last_response.body).to eq 'api_response'
|
121
|
+
end
|
75
122
|
end
|
76
123
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
124
|
+
context 'when the configuration is part of the arguments of a method' do
|
125
|
+
subject(:a_remounted_api) do
|
126
|
+
Class.new(Grape::API) do
|
127
|
+
get configuration[:endpoint_name] do
|
128
|
+
'success'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'mounts the endpoint in the location it is configured' do
|
134
|
+
root_api.mount a_remounted_api, with: { endpoint_name: 'some_location' }
|
135
|
+
get '/some_location'
|
136
|
+
expect(last_response.body).to eq 'success'
|
137
|
+
|
138
|
+
get '/different_location'
|
139
|
+
expect(last_response.status).to eq 404
|
140
|
+
|
141
|
+
root_api.mount a_remounted_api, with: { endpoint_name: 'new_location' }
|
142
|
+
get '/new_location'
|
143
|
+
expect(last_response.body).to eq 'success'
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'when the configuration is the value in a key-arg pair' do
|
147
|
+
subject(:a_remounted_api) do
|
148
|
+
Class.new(Grape::API) do
|
149
|
+
version 'v1', using: :param, parameter: configuration[:version_param]
|
150
|
+
get 'endpoint' do
|
151
|
+
'version 1'
|
152
|
+
end
|
153
|
+
|
154
|
+
version 'v2', using: :param, parameter: configuration[:version_param]
|
155
|
+
get 'endpoint' do
|
156
|
+
'version 2'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'takes the param from the configuration' do
|
162
|
+
root_api.mount a_remounted_api, with: { version_param: 'param_name' }
|
163
|
+
|
164
|
+
get '/endpoint?param_name=v1'
|
165
|
+
expect(last_response.body).to eq 'version 1'
|
166
|
+
|
167
|
+
get '/endpoint?param_name=v2'
|
168
|
+
expect(last_response.body).to eq 'version 2'
|
169
|
+
|
170
|
+
get '/endpoint?wrong_param_name=v2'
|
171
|
+
expect(last_response.body).to eq 'version 1'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'on the DescSCope' do
|
177
|
+
subject(:a_remounted_api) do
|
178
|
+
Class.new(Grape::API) do
|
179
|
+
desc 'The description of this' do
|
180
|
+
tags ['not_configurable_tag', configuration[:a_configurable_tag]]
|
181
|
+
end
|
182
|
+
get 'location' do
|
183
|
+
'success'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'mounts the endpoint with the appropiate tags' do
|
189
|
+
root_api.mount({ a_remounted_api => 'integer' }, with: { a_configurable_tag: 'a configured tag' })
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'on the ParamScope' do
|
194
|
+
subject(:a_remounted_api) do
|
195
|
+
Class.new(Grape::API) do
|
196
|
+
params do
|
197
|
+
requires configuration[:required_param], type: configuration[:required_type]
|
198
|
+
end
|
199
|
+
|
200
|
+
get 'location' do
|
201
|
+
'success'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'mounts the endpoint in the location it is configured' do
|
207
|
+
root_api.mount({ a_remounted_api => 'string' }, with: { required_param: 'param_key', required_type: String })
|
208
|
+
root_api.mount({ a_remounted_api => 'integer' }, with: { required_param: 'param_integer', required_type: Integer })
|
209
|
+
|
210
|
+
get '/string/location', param_key: 'a'
|
211
|
+
expect(last_response.body).to eq 'success'
|
212
|
+
|
213
|
+
get '/string/location', param_integer: 1
|
214
|
+
expect(last_response.status).to eq 400
|
215
|
+
|
216
|
+
get '/integer/location', param_integer: 1
|
217
|
+
expect(last_response.body).to eq 'success'
|
218
|
+
|
219
|
+
get '/integer/location', param_integer: 'a'
|
220
|
+
expect(last_response.status).to eq 400
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'on dynamic checks' do
|
224
|
+
subject(:a_remounted_api) do
|
225
|
+
Class.new(Grape::API) do
|
226
|
+
params do
|
227
|
+
optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] }
|
228
|
+
end
|
229
|
+
|
230
|
+
get 'location' do
|
231
|
+
'success'
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'can read the configuration on lambdas' do
|
237
|
+
root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' }
|
238
|
+
get '/location', restricted_values: 'always'
|
239
|
+
expect(last_response.body).to eq 'success'
|
240
|
+
get '/location', restricted_values: 'sometimes'
|
241
|
+
expect(last_response.body).to eq 'success'
|
242
|
+
get '/location', restricted_values: 'never'
|
243
|
+
expect(last_response.status).to eq 400
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'when the configuration is read within a namespace' do
|
249
|
+
before do
|
250
|
+
a_remounted_api.namespace 'api' do
|
251
|
+
get "/#{configuration[:path]}" do
|
252
|
+
'10 votes'
|
253
|
+
end
|
254
|
+
end
|
255
|
+
root_api.mount a_remounted_api, with: { path: 'votes' }
|
256
|
+
root_api.mount a_remounted_api, with: { path: 'scores' }
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'will use the dynamic configuration on all routes' do
|
260
|
+
get 'api/votes'
|
261
|
+
expect(last_response.body).to eql '10 votes'
|
262
|
+
get 'api/scores'
|
263
|
+
expect(last_response.body).to eql '10 votes'
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'when the configuration is read in a helper' do
|
268
|
+
subject(:a_remounted_api) do
|
269
|
+
Class.new(Grape::API) do
|
270
|
+
helpers do
|
271
|
+
def printed_response
|
272
|
+
configuration[:some_value]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
get 'location' do
|
277
|
+
printed_response
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'will use the dynamic configuration on all routes' do
|
283
|
+
root_api.mount(a_remounted_api, with: { some_value: 'response value' })
|
284
|
+
|
285
|
+
get '/location'
|
286
|
+
expect(last_response.body).to eq 'response value'
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context 'when the configuration is read within the response block' do
|
291
|
+
subject(:a_remounted_api) do
|
292
|
+
Class.new(Grape::API) do
|
293
|
+
get 'location' do
|
294
|
+
configuration[:some_value]
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'will use the dynamic configuration on all routes' do
|
300
|
+
root_api.mount(a_remounted_api, with: { some_value: 'response value' })
|
301
|
+
|
302
|
+
get '/location'
|
303
|
+
expect(last_response.body).to eq 'response value'
|
304
|
+
end
|
82
305
|
end
|
83
306
|
end
|
84
307
|
end
|
data/spec/grape/api_spec.rb
CHANGED
@@ -1628,6 +1628,199 @@ XML
|
|
1628
1628
|
end
|
1629
1629
|
end
|
1630
1630
|
|
1631
|
+
describe 'lifecycle' do
|
1632
|
+
let!(:lifecycle) { [] }
|
1633
|
+
let!(:standard_cycle) do
|
1634
|
+
%i[before before_validation after_validation api_call after finally]
|
1635
|
+
end
|
1636
|
+
|
1637
|
+
let!(:validation_error) do
|
1638
|
+
%i[before before_validation finally]
|
1639
|
+
end
|
1640
|
+
|
1641
|
+
let!(:errored_cycle) do
|
1642
|
+
%i[before before_validation after_validation api_call finally]
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
before do
|
1646
|
+
current_cycle = lifecycle
|
1647
|
+
|
1648
|
+
subject.before do
|
1649
|
+
current_cycle << :before
|
1650
|
+
end
|
1651
|
+
|
1652
|
+
subject.before_validation do
|
1653
|
+
current_cycle << :before_validation
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
subject.after_validation do
|
1657
|
+
current_cycle << :after_validation
|
1658
|
+
end
|
1659
|
+
|
1660
|
+
subject.after do
|
1661
|
+
current_cycle << :after
|
1662
|
+
end
|
1663
|
+
|
1664
|
+
subject.finally do
|
1665
|
+
current_cycle << :finally
|
1666
|
+
end
|
1667
|
+
end
|
1668
|
+
|
1669
|
+
context 'when the api_call succeeds' do
|
1670
|
+
before do
|
1671
|
+
current_cycle = lifecycle
|
1672
|
+
|
1673
|
+
subject.get 'api_call' do
|
1674
|
+
current_cycle << :api_call
|
1675
|
+
end
|
1676
|
+
end
|
1677
|
+
|
1678
|
+
it 'follows the standard life_cycle' do
|
1679
|
+
get '/api_call'
|
1680
|
+
expect(lifecycle).to eq standard_cycle
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
context 'when the api_call has a controlled error' do
|
1685
|
+
before do
|
1686
|
+
current_cycle = lifecycle
|
1687
|
+
|
1688
|
+
subject.get 'api_call' do
|
1689
|
+
current_cycle << :api_call
|
1690
|
+
error!(:some_error)
|
1691
|
+
end
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
it 'follows the errored life_cycle (skips after)' do
|
1695
|
+
get '/api_call'
|
1696
|
+
expect(lifecycle).to eq errored_cycle
|
1697
|
+
end
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
context 'when the api_call has an exception' do
|
1701
|
+
before do
|
1702
|
+
current_cycle = lifecycle
|
1703
|
+
|
1704
|
+
subject.get 'api_call' do
|
1705
|
+
current_cycle << :api_call
|
1706
|
+
raise StandardError
|
1707
|
+
end
|
1708
|
+
end
|
1709
|
+
|
1710
|
+
it 'follows the errored life_cycle (skips after)' do
|
1711
|
+
expect { get '/api_call' }.to raise_error(StandardError)
|
1712
|
+
expect(lifecycle).to eq errored_cycle
|
1713
|
+
end
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
context 'when the api_call fails validation' do
|
1717
|
+
before do
|
1718
|
+
current_cycle = lifecycle
|
1719
|
+
|
1720
|
+
subject.params do
|
1721
|
+
requires :some_param, type: String
|
1722
|
+
end
|
1723
|
+
|
1724
|
+
subject.get 'api_call' do
|
1725
|
+
current_cycle << :api_call
|
1726
|
+
end
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do
|
1730
|
+
get '/api_call'
|
1731
|
+
expect(lifecycle).to eq validation_error
|
1732
|
+
end
|
1733
|
+
end
|
1734
|
+
end
|
1735
|
+
|
1736
|
+
describe '.finally' do
|
1737
|
+
let!(:code) { { has_executed: false } }
|
1738
|
+
let(:block_to_run) do
|
1739
|
+
code_to_execute = code
|
1740
|
+
proc do
|
1741
|
+
code_to_execute[:has_executed] = true
|
1742
|
+
end
|
1743
|
+
end
|
1744
|
+
|
1745
|
+
context 'when the ensure block has no exceptions' do
|
1746
|
+
before { subject.finally(&block_to_run) }
|
1747
|
+
|
1748
|
+
context 'when no API call is made' do
|
1749
|
+
it 'has not executed the ensure code' do
|
1750
|
+
expect(code[:has_executed]).to be false
|
1751
|
+
end
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
context 'when no errors occurs' do
|
1755
|
+
before do
|
1756
|
+
subject.get '/no_exceptions' do
|
1757
|
+
'success'
|
1758
|
+
end
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
it 'executes the ensure code' do
|
1762
|
+
get '/no_exceptions'
|
1763
|
+
expect(last_response.body).to eq 'success'
|
1764
|
+
expect(code[:has_executed]).to be true
|
1765
|
+
end
|
1766
|
+
|
1767
|
+
context 'with a helper' do
|
1768
|
+
let(:block_to_run) do
|
1769
|
+
code_to_execute = code
|
1770
|
+
proc do
|
1771
|
+
code_to_execute[:value] = some_helper
|
1772
|
+
end
|
1773
|
+
end
|
1774
|
+
|
1775
|
+
before do
|
1776
|
+
subject.helpers do
|
1777
|
+
def some_helper
|
1778
|
+
'some_value'
|
1779
|
+
end
|
1780
|
+
end
|
1781
|
+
|
1782
|
+
subject.get '/with_helpers' do
|
1783
|
+
'success'
|
1784
|
+
end
|
1785
|
+
end
|
1786
|
+
|
1787
|
+
it 'has access to the helper' do
|
1788
|
+
get '/with_helpers'
|
1789
|
+
expect(code[:value]).to eq 'some_value'
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
end
|
1793
|
+
|
1794
|
+
context 'when an unhandled occurs inside the API call' do
|
1795
|
+
before do
|
1796
|
+
subject.get '/unhandled_exception' do
|
1797
|
+
raise StandardError
|
1798
|
+
end
|
1799
|
+
end
|
1800
|
+
|
1801
|
+
it 'executes the ensure code' do
|
1802
|
+
expect { get '/unhandled_exception' }.to raise_error StandardError
|
1803
|
+
expect(code[:has_executed]).to be true
|
1804
|
+
end
|
1805
|
+
end
|
1806
|
+
|
1807
|
+
context 'when a handled error occurs inside the API call' do
|
1808
|
+
before do
|
1809
|
+
subject.rescue_from(StandardError) { error! 'handled' }
|
1810
|
+
subject.get '/handled_exception' do
|
1811
|
+
raise StandardError
|
1812
|
+
end
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
it 'executes the ensure code' do
|
1816
|
+
get '/handled_exception'
|
1817
|
+
expect(code[:has_executed]).to be true
|
1818
|
+
expect(last_response.body).to eq 'handled'
|
1819
|
+
end
|
1820
|
+
end
|
1821
|
+
end
|
1822
|
+
end
|
1823
|
+
|
1631
1824
|
describe '.rescue_from' do
|
1632
1825
|
it 'does not rescue errors when rescue_from is not set' do
|
1633
1826
|
subject.get '/exception' do
|
@@ -3676,4 +3869,21 @@ XML
|
|
3676
3869
|
expect(grape_api.eql?(MyAPI))
|
3677
3870
|
end
|
3678
3871
|
end
|
3872
|
+
|
3873
|
+
describe 'const_missing' do
|
3874
|
+
subject(:grape_api) { Class.new(Grape::API) }
|
3875
|
+
let(:mounted) do
|
3876
|
+
Class.new(Grape::API) do
|
3877
|
+
get '/missing' do
|
3878
|
+
SomeRandomConstant
|
3879
|
+
end
|
3880
|
+
end
|
3881
|
+
end
|
3882
|
+
|
3883
|
+
before { subject.mount mounted => '/const' }
|
3884
|
+
|
3885
|
+
it 'raises an error' do
|
3886
|
+
expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
|
3887
|
+
end
|
3888
|
+
end
|
3679
3889
|
end
|