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.
@@ -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(:aliased_params, @api.route_setting(:aliased_params) || [])
123
- @api.route_setting(:aliased_params) << { attrs.first => opts[:as] }
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
- @alias = options
5
+ @renamed_options = options
6
6
  super
7
7
  end
8
8
 
9
9
  def validate_param!(attr_name, params)
10
- params[@alias] = params[attr_name]
10
+ params[@renamed_options] = params[attr_name]
11
11
  end
12
12
  end
13
13
  end
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '1.2.3'.freeze
3
+ VERSION = '1.2.4'.freeze
4
4
  end
Binary file
@@ -63,22 +63,245 @@ describe Grape::API do
63
63
  end
64
64
  end
65
65
 
66
- context 'with a dynamically configured route' do
67
- before do
68
- a_remounted_api.namespace 'api' do
69
- get "/#{configuration[:path]}" do
70
- '10 votes'
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
- root_api.mount a_remounted_api, with: { path: 'votes' }
74
- root_api.mount a_remounted_api, with: { path: 'scores' }
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
- it 'will use the dynamic configuration on all routes' do
78
- get 'api/votes'
79
- expect(last_response.body).to eql '10 votes'
80
- get 'api/scores'
81
- expect(last_response.body).to eql '10 votes'
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
@@ -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