grape 0.12.0 → 0.14.0

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.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +265 -215
  4. data/CONTRIBUTING.md +4 -4
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +166 -0
  7. data/README.md +426 -161
  8. data/RELEASING.md +14 -6
  9. data/Rakefile +30 -33
  10. data/UPGRADING.md +54 -23
  11. data/benchmark/simple.rb +27 -0
  12. data/gemfiles/rack_1.5.2.gemfile +13 -0
  13. data/gemfiles/rails_3.gemfile +2 -2
  14. data/gemfiles/rails_4.gemfile +1 -2
  15. data/grape.gemspec +6 -7
  16. data/lib/grape/api.rb +24 -4
  17. data/lib/grape/dsl/callbacks.rb +20 -0
  18. data/lib/grape/dsl/configuration.rb +59 -2
  19. data/lib/grape/dsl/helpers.rb +8 -3
  20. data/lib/grape/dsl/inside_route.rb +100 -45
  21. data/lib/grape/dsl/parameters.rb +96 -7
  22. data/lib/grape/dsl/request_response.rb +1 -1
  23. data/lib/grape/dsl/routing.rb +17 -4
  24. data/lib/grape/dsl/settings.rb +36 -1
  25. data/lib/grape/dsl/validations.rb +7 -5
  26. data/lib/grape/endpoint.rb +102 -57
  27. data/lib/grape/error_formatter/base.rb +6 -6
  28. data/lib/grape/exceptions/base.rb +5 -5
  29. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  30. data/lib/grape/exceptions/unknown_parameter.rb +10 -0
  31. data/lib/grape/exceptions/validation_errors.rb +4 -3
  32. data/lib/grape/formatter/serializable_hash.rb +3 -2
  33. data/lib/grape/http/headers.rb +0 -1
  34. data/lib/grape/locale/en.yml +5 -1
  35. data/lib/grape/middleware/auth/base.rb +2 -2
  36. data/lib/grape/middleware/auth/dsl.rb +1 -1
  37. data/lib/grape/middleware/auth/strategies.rb +1 -1
  38. data/lib/grape/middleware/base.rb +8 -4
  39. data/lib/grape/middleware/error.rb +3 -2
  40. data/lib/grape/middleware/filter.rb +1 -1
  41. data/lib/grape/middleware/formatter.rb +64 -45
  42. data/lib/grape/middleware/globals.rb +3 -3
  43. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  44. data/lib/grape/middleware/versioner/header.rb +113 -50
  45. data/lib/grape/middleware/versioner/param.rb +5 -8
  46. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  47. data/lib/grape/middleware/versioner/path.rb +3 -6
  48. data/lib/grape/namespace.rb +13 -2
  49. data/lib/grape/path.rb +4 -3
  50. data/lib/grape/request.rb +40 -0
  51. data/lib/grape/route.rb +5 -0
  52. data/lib/grape/util/content_types.rb +9 -9
  53. data/lib/grape/util/env.rb +22 -0
  54. data/lib/grape/util/file_response.rb +21 -0
  55. data/lib/grape/util/inheritable_setting.rb +23 -2
  56. data/lib/grape/util/inheritable_values.rb +1 -1
  57. data/lib/grape/util/stackable_values.rb +5 -2
  58. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  59. data/lib/grape/validations/attributes_iterator.rb +8 -3
  60. data/lib/grape/validations/params_scope.rb +164 -22
  61. data/lib/grape/validations/types/build_coercer.rb +53 -0
  62. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  63. data/lib/grape/validations/types/file.rb +28 -0
  64. data/lib/grape/validations/types/json.rb +65 -0
  65. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  66. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  67. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  68. data/lib/grape/validations/types.rb +144 -0
  69. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  70. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  71. data/lib/grape/validations/validators/base.rb +7 -0
  72. data/lib/grape/validations/validators/coerce.rb +32 -34
  73. data/lib/grape/validations/validators/presence.rb +2 -3
  74. data/lib/grape/validations/validators/regexp.rb +2 -4
  75. data/lib/grape/validations/validators/values.rb +3 -3
  76. data/lib/grape/validations.rb +5 -0
  77. data/lib/grape/version.rb +2 -1
  78. data/lib/grape.rb +15 -12
  79. data/pkg/grape-0.13.0.gem +0 -0
  80. data/spec/grape/api/custom_validations_spec.rb +5 -4
  81. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  82. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  83. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  84. data/spec/grape/api_spec.rb +151 -54
  85. data/spec/grape/dsl/configuration_spec.rb +13 -0
  86. data/spec/grape/dsl/helpers_spec.rb +16 -2
  87. data/spec/grape/dsl/inside_route_spec.rb +40 -4
  88. data/spec/grape/dsl/parameters_spec.rb +0 -6
  89. data/spec/grape/dsl/routing_spec.rb +1 -1
  90. data/spec/grape/dsl/validations_spec.rb +18 -0
  91. data/spec/grape/endpoint_spec.rb +130 -6
  92. data/spec/grape/entity_spec.rb +10 -8
  93. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  94. data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
  95. data/spec/grape/integration/rack_spec.rb +3 -2
  96. data/spec/grape/middleware/base_spec.rb +40 -16
  97. data/spec/grape/middleware/error_spec.rb +16 -15
  98. data/spec/grape/middleware/exception_spec.rb +45 -43
  99. data/spec/grape/middleware/formatter_spec.rb +34 -5
  100. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  101. data/spec/grape/path_spec.rb +10 -10
  102. data/spec/grape/presenters/presenter_spec.rb +2 -2
  103. data/spec/grape/request_spec.rb +100 -0
  104. data/spec/grape/util/inheritable_values_spec.rb +14 -0
  105. data/spec/grape/util/stackable_values_spec.rb +10 -0
  106. data/spec/grape/validations/params_scope_spec.rb +86 -0
  107. data/spec/grape/validations/types_spec.rb +95 -0
  108. data/spec/grape/validations/validators/coerce_spec.rb +364 -10
  109. data/spec/grape/validations/validators/values_spec.rb +27 -15
  110. data/spec/grape/validations_spec.rb +53 -24
  111. data/spec/shared/versioning_examples.rb +2 -2
  112. data/spec/spec_helper.rb +0 -1
  113. data/spec/support/versioned_helpers.rb +2 -2
  114. metadata +55 -14
  115. data/.gitignore +0 -46
  116. data/.rspec +0 -2
  117. data/.rubocop.yml +0 -7
  118. data/.rubocop_todo.yml +0 -84
  119. data/.travis.yml +0 -20
  120. data/.yardopts +0 -2
  121. data/lib/backports/active_support/deep_dup.rb +0 -49
  122. data/lib/backports/active_support/duplicable.rb +0 -88
  123. data/lib/grape/http/request.rb +0 -27
@@ -110,11 +110,6 @@ describe Grape::Middleware::Formatter do
110
110
  expect(subject.env['api.format']).to eq(:xml)
111
111
  end
112
112
 
113
- it 'looks for case-indifferent headers' do
114
- subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml')
115
- expect(subject.env['api.format']).to eq(:xml)
116
- end
117
-
118
113
  it 'uses quality rankings to determine formats' do
119
114
  subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0')
120
115
  expect(subject.env['api.format']).to eq(:xml)
@@ -137,6 +132,18 @@ describe Grape::Middleware::Formatter do
137
132
  expect(subject.env['api.format']).to eq(:xml)
138
133
  end
139
134
 
135
+ context 'with custom vendored content types' do
136
+ before do
137
+ subject.options[:content_types] = {}
138
+ subject.options[:content_types][:custom] = 'application/vnd.test+json'
139
+ end
140
+
141
+ it 'it uses the custom type' do
142
+ subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
143
+ expect(subject.env['api.format']).to eq(:custom)
144
+ end
145
+ end
146
+
140
147
  it 'parses headers with symbols as hash keys' do
141
148
  subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml', system_time: '091293')
142
149
  expect(subject.env[:system_time]).to eq('091293')
@@ -162,6 +169,16 @@ describe Grape::Middleware::Formatter do
162
169
  _, headers, = subject.call('PATH_INFO' => '/info.custom')
163
170
  expect(headers['Content-type']).to eq('application/x-custom')
164
171
  end
172
+ it 'is set for vendored with registered type' do
173
+ subject.options[:content_types] = {}
174
+ subject.options[:content_types][:custom] = 'application/vnd.test+json'
175
+ _, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
176
+ expect(headers['Content-type']).to eq('application/vnd.test+json')
177
+ end
178
+ it 'is set to closest generic for custom vendored/versioned without registered type' do
179
+ _, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
180
+ expect(headers['Content-type']).to eq('application/json')
181
+ end
165
182
  end
166
183
 
167
184
  context 'format' do
@@ -184,6 +201,18 @@ describe Grape::Middleware::Formatter do
184
201
  end
185
202
  end
186
203
 
204
+ context 'no content responses' do
205
+ let(:no_content_response) { ->(status) { [status, {}, ['']] } }
206
+
207
+ Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.each do |status|
208
+ it "does not modify a #{status} response" do
209
+ expected_response = no_content_response[status]
210
+ allow(app).to receive(:call).and_return(expected_response)
211
+ expect(subject.call({})).to eq(expected_response)
212
+ end
213
+ end
214
+ end
215
+
187
216
  context 'input' do
188
217
  %w(POST PATCH PUT DELETE).each do |method|
189
218
  ['application/json', 'application/json; charset=utf-8'].each do |content_type|
@@ -56,13 +56,13 @@ describe Grape::Middleware::Versioner::Header do
56
56
  end
57
57
 
58
58
  it 'is set' do
59
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
59
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
60
60
  expect(env['api.format']).to eql 'json'
61
61
  expect(status).to eq(200)
62
62
  end
63
63
 
64
64
  it 'is nil if not provided' do
65
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
65
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
66
66
  expect(env['api.format']).to eql nil
67
67
  expect(status).to eq(200)
68
68
  end
@@ -72,13 +72,13 @@ describe Grape::Middleware::Versioner::Header do
72
72
 
73
73
  context 'api.vendor' do
74
74
  it 'is set' do
75
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
75
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
76
76
  expect(env['api.vendor']).to eql 'vendor'
77
77
  expect(status).to eq(200)
78
78
  end
79
79
 
80
80
  it 'is set if format provided' do
81
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
81
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
82
82
  expect(env['api.vendor']).to eql 'vendor'
83
83
  expect(status).to eq(200)
84
84
  end
@@ -89,7 +89,7 @@ describe Grape::Middleware::Versioner::Header do
89
89
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
90
90
  expect(exception.headers).to eql('X-Cascade' => 'pass')
91
91
  expect(exception.status).to eql 406
92
- expect(exception.message).to include 'API vendor or version not found'
92
+ expect(exception.message).to include 'API vendor not found'
93
93
  end
94
94
  end
95
95
 
@@ -99,13 +99,13 @@ describe Grape::Middleware::Versioner::Header do
99
99
  end
100
100
 
101
101
  it 'is set' do
102
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
102
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
103
103
  expect(env['api.vendor']).to eql 'vendor'
104
104
  expect(status).to eq(200)
105
105
  end
106
106
 
107
107
  it 'is set if format provided' do
108
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
108
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
109
109
  expect(env['api.vendor']).to eql 'vendor'
110
110
  expect(status).to eq(200)
111
111
  end
@@ -116,7 +116,7 @@ describe Grape::Middleware::Versioner::Header do
116
116
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
117
117
  expect(exception.headers).to eql('X-Cascade' => 'pass')
118
118
  expect(exception.status).to eql 406
119
- expect(exception.message).to include('API vendor or version not found')
119
+ expect(exception.message).to include('API vendor not found')
120
120
  end
121
121
  end
122
122
  end
@@ -128,23 +128,23 @@ describe Grape::Middleware::Versioner::Header do
128
128
  end
129
129
 
130
130
  it 'is set' do
131
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
131
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
132
132
  expect(env['api.version']).to eql 'v1'
133
133
  expect(status).to eq(200)
134
134
  end
135
135
 
136
136
  it 'is set if format provided' do
137
- status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
137
+ status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
138
138
  expect(env['api.version']).to eql 'v1'
139
139
  expect(status).to eq(200)
140
140
  end
141
141
 
142
142
  it 'fails with 406 Not Acceptable if version is invalid' do
143
143
  expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').last }.to raise_exception do |exception|
144
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
144
+ expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
145
145
  expect(exception.headers).to eql('X-Cascade' => 'pass')
146
146
  expect(exception.status).to eql 406
147
- expect(exception.message).to include('API vendor or version not found')
147
+ expect(exception.message).to include('API version not found')
148
148
  end
149
149
  end
150
150
  end
@@ -184,24 +184,6 @@ describe Grape::Middleware::Versioner::Header do
184
184
  end
185
185
  end
186
186
 
187
- it 'fails with 406 Not Acceptable if type is a range' do
188
- expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
189
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
190
- expect(exception.headers).to eql('X-Cascade' => 'pass')
191
- expect(exception.status).to eql 406
192
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
193
- end
194
- end
195
-
196
- it 'fails with 406 Not Acceptable if subtype is a range' do
197
- expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
198
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
199
- expect(exception.headers).to eql('X-Cascade' => 'pass')
200
- expect(exception.status).to eql 406
201
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
202
- end
203
- end
204
-
205
187
  it 'succeeds if proper header is set' do
206
188
  expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)
207
189
  end
@@ -223,30 +205,22 @@ describe Grape::Middleware::Versioner::Header do
223
205
  end
224
206
  end
225
207
 
226
- it 'fails with 406 Not Acceptable if header is empty' do
227
- expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
228
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
229
- expect(exception.headers).to eql({})
230
- expect(exception.status).to eql 406
231
- expect(exception.message).to include('Accept header must be set.')
232
- end
233
- end
234
-
235
- it 'fails with 406 Not Acceptable if type is a range' do
236
- expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
208
+ it 'fails with 406 Not Acceptable if header is application/xml' do
209
+ expect { subject.call('HTTP_ACCEPT' => 'application/xml').last }
210
+ .to raise_exception do |exception|
237
211
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
238
212
  expect(exception.headers).to eql({})
239
213
  expect(exception.status).to eql 406
240
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
214
+ expect(exception.message).to include('API vendor or version not found.')
241
215
  end
242
216
  end
243
217
 
244
- it 'fails with 406 Not Acceptable if subtype is a range' do
245
- expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
218
+ it 'fails with 406 Not Acceptable if header is empty' do
219
+ expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
246
220
  expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
247
221
  expect(exception.headers).to eql({})
248
222
  expect(exception.status).to eql 406
249
- expect(exception.message).to include('Accept header must not contain ranges ("*").')
223
+ expect(exception.message).to include('Accept header must be set.')
250
224
  end
251
225
  end
252
226
 
@@ -270,10 +244,68 @@ describe Grape::Middleware::Versioner::Header do
270
244
 
271
245
  it 'fails with another version' do
272
246
  expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json') }.to raise_exception do |exception|
273
- expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
247
+ expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
274
248
  expect(exception.headers).to eql('X-Cascade' => 'pass')
275
249
  expect(exception.status).to eql 406
276
- expect(exception.message).to include('API vendor or version not found')
250
+ expect(exception.message).to include('API version not found')
251
+ end
252
+ end
253
+ end
254
+
255
+ context 'when there are multiple versions with complex vendor specified with rescue_from :all' do
256
+ subject {
257
+ Class.new(Grape::API) do
258
+ rescue_from :all
259
+ end
260
+ }
261
+
262
+ let(:v1_app) {
263
+ Class.new(Grape::API) do
264
+ version 'v1', using: :header, vendor: 'test.a-cool_resource', cascade: false, strict: true
265
+ content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'
266
+ formatter :v1_test, ->(object, _) { object }
267
+ format :v1_test
268
+
269
+ resources :users do
270
+ get :hello do
271
+ 'one'
272
+ end
273
+ end
274
+ end
275
+ }
276
+
277
+ let(:v2_app) {
278
+ Class.new(Grape::API) do
279
+ version 'v2', using: :header, vendor: 'test.a-cool_resource', strict: true
280
+ content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'
281
+ formatter :v2_test, ->(object, _) { object }
282
+ format :v2_test
283
+
284
+ resources :users do
285
+ get :hello do
286
+ 'two'
287
+ end
288
+ end
289
+ end
290
+ }
291
+
292
+ def app
293
+ subject.mount v2_app
294
+ subject.mount v1_app
295
+ subject
296
+ end
297
+
298
+ context 'with header versioned endpoints and a rescue_all block defined' do
299
+ it 'responds correctly to a v1 request' do
300
+ versioned_get '/users/hello', 'v1', using: :header, vendor: 'test.a-cool_resource'
301
+ expect(last_response.body).to eq('one')
302
+ expect(last_response.body).not_to include('API vendor or version not found')
303
+ end
304
+
305
+ it 'responds correctly to a v2 request' do
306
+ versioned_get '/users/hello', 'v2', using: :header, vendor: 'test.a-cool_resource'
307
+ expect(last_response.body).to eq('two')
308
+ expect(last_response.body).not_to include('API vendor or version not found')
277
309
  end
278
310
  end
279
311
  end
@@ -82,47 +82,47 @@ module Grape
82
82
  end
83
83
  end
84
84
 
85
- describe '#has_namespace?' do
85
+ describe '#namespace?' do
86
86
  it 'is false when the namespace is nil' do
87
87
  path = Path.new(anything, nil, anything)
88
- expect(path).not_to have_namespace
88
+ expect(path.namespace?).to be nil
89
89
  end
90
90
 
91
91
  it 'is false when the namespace starts with whitespace' do
92
92
  path = Path.new(anything, ' /foo', anything)
93
- expect(path).not_to have_namespace
93
+ expect(path.namespace?).to be nil
94
94
  end
95
95
 
96
96
  it 'is false when the namespace is the root path' do
97
97
  path = Path.new(anything, '/', anything)
98
- expect(path).not_to have_namespace
98
+ expect(path.namespace?).to be false
99
99
  end
100
100
 
101
101
  it 'is true otherwise' do
102
102
  path = Path.new(anything, '/world', anything)
103
- expect(path).to have_namespace
103
+ expect(path.namespace?).to be true
104
104
  end
105
105
  end
106
106
 
107
- describe '#has_path?' do
107
+ describe '#path?' do
108
108
  it 'is false when the path is nil' do
109
109
  path = Path.new(nil, anything, anything)
110
- expect(path).not_to have_path
110
+ expect(path.path?).to be nil
111
111
  end
112
112
 
113
113
  it 'is false when the path starts with whitespace' do
114
114
  path = Path.new(' /foo', anything, anything)
115
- expect(path).not_to have_path
115
+ expect(path.path?).to be nil
116
116
  end
117
117
 
118
118
  it 'is false when the path is the root path' do
119
119
  path = Path.new('/', anything, anything)
120
- expect(path).not_to have_path
120
+ expect(path.path?).to be false
121
121
  end
122
122
 
123
123
  it 'is true otherwise' do
124
124
  path = Path.new('/hello', anything, anything)
125
- expect(path).to have_path
125
+ expect(path.path?).to be true
126
126
  end
127
127
  end
128
128
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  module Grape
4
4
  module Presenters
5
- module InsideRouteSpec
5
+ module PresenterSpec
6
6
  class Dummy
7
7
  include Grape::DSL::InsideRoute
8
8
 
@@ -27,7 +27,7 @@ module Grape
27
27
  end
28
28
  end
29
29
 
30
- subject { InsideRouteSpec::Dummy.new }
30
+ subject { PresenterSpec::Dummy.new }
31
31
 
32
32
  describe 'present' do
33
33
  let(:hash_mock) do
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ module Grape
4
+ describe Request do
5
+ let(:default_method) { 'GET' }
6
+ let(:default_params) { {} }
7
+ let(:default_options) {
8
+ {
9
+ method: method,
10
+ params: params
11
+ }
12
+ }
13
+ let(:default_env) {
14
+ Rack::MockRequest.env_for('/', options)
15
+ }
16
+ let(:method) { default_method }
17
+ let(:params) { default_params }
18
+ let(:options) { default_options }
19
+ let(:env) { default_env }
20
+
21
+ let(:request) {
22
+ Grape::Request.new(env)
23
+ }
24
+
25
+ describe '#params' do
26
+ let(:params) {
27
+ {
28
+ a: '123',
29
+ b: 'xyz'
30
+ }
31
+ }
32
+
33
+ it 'returns params' do
34
+ expect(request.params).to eq('a' => '123', 'b' => 'xyz')
35
+ end
36
+
37
+ describe 'with rack.routing_args' do
38
+ let(:options) {
39
+ default_options.merge('rack.routing_args' => routing_args)
40
+ }
41
+ let(:routing_args) {
42
+ {
43
+ version: '123',
44
+ route_info: '456',
45
+ c: 'ccc'
46
+ }
47
+ }
48
+
49
+ it 'cuts version and route_info' do
50
+ expect(request.params).to eq('a' => '123', 'b' => 'xyz', 'c' => 'ccc')
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#headers' do
56
+ let(:options) {
57
+ default_options.merge(request_headers)
58
+ }
59
+
60
+ describe 'with http headers in env' do
61
+ let(:request_headers) {
62
+ {
63
+ 'HTTP_X_GRAPE_IS_COOL' => 'yeah'
64
+ }
65
+ }
66
+
67
+ it 'cuts HTTP_ prefix and capitalizes header name words' do
68
+ expect(request.headers).to eq('X-Grape-Is-Cool' => 'yeah')
69
+ end
70
+ end
71
+
72
+ describe 'with non-HTTP_* stuff in env' do
73
+ let(:request_headers) {
74
+ {
75
+ 'HTP_X_GRAPE_ENTITY_TOO' => 'but now we are testing Grape'
76
+ }
77
+ }
78
+
79
+ it 'does not include them' do
80
+ expect(request.headers).to eq({})
81
+ end
82
+ end
83
+
84
+ describe 'with symbolic header names' do
85
+ let(:request_headers) {
86
+ {
87
+ HTTP_GRAPE_LIKES_SYMBOLIC: 'it is true'
88
+ }
89
+ }
90
+ let(:env) {
91
+ default_env.merge(request_headers)
92
+ }
93
+
94
+ it 'converts them to string' do
95
+ expect(request.headers).to eq('Grape-Likes-Symbolic' => 'it is true')
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -58,6 +58,20 @@ module Grape
58
58
  expect(subject.to_hash).to eq(some_thing: :foo, some_thing_more: :foo_bar)
59
59
  end
60
60
  end
61
+
62
+ describe '#clone' do
63
+ let(:obj_cloned) { subject.clone }
64
+
65
+ context 'complex (i.e. not primitive) data types (ex. entity classes, please see bug #891)' do
66
+ let(:description) { { entity: double } }
67
+
68
+ before { subject[:description] = description }
69
+
70
+ it 'copies values; does not duplicate them' do
71
+ expect(obj_cloned[:description]).to eq description
72
+ end
73
+ end
74
+ end
61
75
  end
62
76
  end
63
77
  end
@@ -109,6 +109,16 @@ module Grape
109
109
 
110
110
  expect(grandchild.clone.to_hash).to eq(some_thing: [:foo, [:bar, :more], :grand_foo_bar], some_thing_more: [:foo_bar])
111
111
  end
112
+
113
+ context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do
114
+ let(:middleware) { double }
115
+
116
+ before { subject[:middleware] = middleware }
117
+
118
+ it 'copies values; does not duplicate them' do
119
+ expect(obj_cloned[:middleware]).to eq [middleware]
120
+ end
121
+ end
112
122
  end
113
123
  end
114
124
  end
@@ -89,6 +89,37 @@ describe Grape::Validations::ParamsScope do
89
89
  end
90
90
  end
91
91
 
92
+ context 'when using custom types' do
93
+ module ParamsScopeSpec
94
+ class CustomType
95
+ attr_reader :value
96
+ def self.parse(value)
97
+ fail if value == 'invalid'
98
+ new(value)
99
+ end
100
+
101
+ def initialize(value)
102
+ @value = value
103
+ end
104
+ end
105
+ end
106
+
107
+ it 'coerces the parameter via the type\'s parse method' do
108
+ subject.params do
109
+ requires :foo, type: ParamsScopeSpec::CustomType
110
+ end
111
+ subject.get('/types') { params[:foo].value }
112
+
113
+ get '/types', foo: 'valid'
114
+ expect(last_response.status).to eq(200)
115
+ expect(last_response.body).to eq('valid')
116
+
117
+ get '/types', foo: 'invalid'
118
+ expect(last_response.status).to eq(400)
119
+ expect(last_response.body).to match(/foo is invalid/)
120
+ end
121
+ end
122
+
92
123
  context 'array without coerce type explicitly given' do
93
124
  it 'sets the type based on first element' do
94
125
  subject.params do
@@ -242,4 +273,59 @@ describe Grape::Validations::ParamsScope do
242
273
  end.to raise_error Grape::Exceptions::UnsupportedGroupTypeError
243
274
  end
244
275
  end
276
+
277
+ context 'when validations are dependent on a parameter' do
278
+ before do
279
+ subject.params do
280
+ optional :a
281
+ given :a do
282
+ requires :b
283
+ end
284
+ end
285
+ subject.get('/test') { declared(params).to_json }
286
+ end
287
+
288
+ it 'applies the validations only if the parameter is present' do
289
+ get '/test'
290
+ expect(last_response.status).to eq(200)
291
+
292
+ get '/test', a: true
293
+ expect(last_response.status).to eq(400)
294
+ expect(last_response.body).to eq('b is missing')
295
+
296
+ get '/test', a: true, b: true
297
+ expect(last_response.status).to eq(200)
298
+ end
299
+
300
+ it 'raises an error if the dependent parameter was never specified' do
301
+ expect do
302
+ subject.params do
303
+ given :c do
304
+ end
305
+ end
306
+ end.to raise_error(Grape::Exceptions::UnknownParameter)
307
+ end
308
+
309
+ it 'includes the parameter within #declared(params)' do
310
+ get '/test', a: true, b: true
311
+
312
+ expect(JSON.parse(last_response.body)).to eq('a' => 'true', 'b' => 'true')
313
+ end
314
+
315
+ it 'returns a sensible error message within a nested context' do
316
+ subject.params do
317
+ requires :bar, type: Hash do
318
+ optional :a
319
+ given :a do
320
+ requires :b
321
+ end
322
+ end
323
+ end
324
+ subject.get('/nested') { 'worked' }
325
+
326
+ get '/nested', bar: { a: true }
327
+ expect(last_response.status).to eq(400)
328
+ expect(last_response.body).to eq('bar[b] is missing')
329
+ end
330
+ end
245
331
  end