grape 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +23 -80
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +27 -0
  5. data/Gemfile +1 -1
  6. data/Guardfile +1 -1
  7. data/LICENSE +1 -1
  8. data/README.md +131 -30
  9. data/Rakefile +1 -1
  10. data/UPGRADING.md +110 -1
  11. data/gemfiles/rails_3.gemfile +1 -1
  12. data/gemfiles/rails_4.gemfile +1 -1
  13. data/grape.gemspec +4 -4
  14. data/lib/grape.rb +92 -62
  15. data/lib/grape/api.rb +10 -10
  16. data/lib/grape/cookies.rb +1 -1
  17. data/lib/grape/dsl/configuration.rb +7 -7
  18. data/lib/grape/dsl/helpers.rb +3 -3
  19. data/lib/grape/dsl/inside_route.rb +50 -21
  20. data/lib/grape/dsl/parameters.rb +25 -6
  21. data/lib/grape/dsl/request_response.rb +1 -1
  22. data/lib/grape/dsl/routing.rb +11 -10
  23. data/lib/grape/dsl/settings.rb +1 -1
  24. data/lib/grape/endpoint.rb +21 -19
  25. data/lib/grape/error_formatter/json.rb +1 -1
  26. data/lib/grape/exceptions/base.rb +1 -1
  27. data/lib/grape/exceptions/validation.rb +1 -1
  28. data/lib/grape/exceptions/validation_errors.rb +2 -2
  29. data/lib/grape/formatter/base.rb +1 -1
  30. data/lib/grape/formatter/json.rb +1 -1
  31. data/lib/grape/formatter/serializable_hash.rb +4 -4
  32. data/lib/grape/formatter/txt.rb +1 -1
  33. data/lib/grape/formatter/xml.rb +1 -1
  34. data/lib/grape/http/headers.rb +27 -0
  35. data/lib/grape/http/request.rb +1 -1
  36. data/lib/grape/middleware/error.rb +10 -4
  37. data/lib/grape/middleware/formatter.rb +13 -9
  38. data/lib/grape/middleware/globals.rb +2 -1
  39. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
  40. data/lib/grape/middleware/versioner/header.rb +4 -4
  41. data/lib/grape/middleware/versioner/param.rb +2 -2
  42. data/lib/grape/middleware/versioner/path.rb +1 -1
  43. data/lib/grape/namespace.rb +2 -1
  44. data/lib/grape/parser/json.rb +1 -1
  45. data/lib/grape/parser/xml.rb +1 -1
  46. data/lib/grape/path.rb +3 -3
  47. data/lib/grape/presenters/presenter.rb +9 -0
  48. data/lib/grape/validations/params_scope.rb +3 -3
  49. data/lib/grape/validations/validators/allow_blank.rb +1 -1
  50. data/lib/grape/validations/validators/coerce.rb +6 -5
  51. data/lib/grape/validations/validators/default.rb +2 -2
  52. data/lib/grape/validations/validators/multiple_params_base.rb +1 -0
  53. data/lib/grape/validations/validators/regexp.rb +1 -1
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/custom_validations_spec.rb +47 -0
  56. data/spec/grape/api/deeply_included_options_spec.rb +56 -0
  57. data/spec/grape/api_spec.rb +64 -42
  58. data/spec/grape/dsl/configuration_spec.rb +2 -2
  59. data/spec/grape/dsl/helpers_spec.rb +1 -1
  60. data/spec/grape/dsl/inside_route_spec.rb +75 -19
  61. data/spec/grape/dsl/parameters_spec.rb +59 -10
  62. data/spec/grape/dsl/request_response_spec.rb +62 -2
  63. data/spec/grape/dsl/routing_spec.rb +116 -18
  64. data/spec/grape/endpoint_spec.rb +57 -5
  65. data/spec/grape/entity_spec.rb +1 -1
  66. data/spec/grape/exceptions/body_parse_errors_spec.rb +5 -5
  67. data/spec/grape/exceptions/invalid_accept_header_spec.rb +32 -32
  68. data/spec/grape/exceptions/validation_errors_spec.rb +1 -1
  69. data/spec/grape/integration/rack_spec.rb +4 -3
  70. data/spec/grape/middleware/auth/strategies_spec.rb +2 -2
  71. data/spec/grape/middleware/base_spec.rb +2 -2
  72. data/spec/grape/middleware/error_spec.rb +1 -1
  73. data/spec/grape/middleware/exception_spec.rb +5 -5
  74. data/spec/grape/middleware/formatter_spec.rb +10 -10
  75. data/spec/grape/middleware/globals_spec.rb +27 -0
  76. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
  77. data/spec/grape/middleware/versioner/header_spec.rb +1 -1
  78. data/spec/grape/middleware/versioner/param_spec.rb +1 -1
  79. data/spec/grape/middleware/versioner/path_spec.rb +1 -1
  80. data/spec/grape/path_spec.rb +6 -4
  81. data/spec/grape/presenters/presenter_spec.rb +70 -0
  82. data/spec/grape/util/inheritable_values_spec.rb +1 -1
  83. data/spec/grape/util/stackable_values_spec.rb +1 -1
  84. data/spec/grape/util/strict_hash_configuration_spec.rb +1 -1
  85. data/spec/grape/validations/params_scope_spec.rb +64 -0
  86. data/spec/grape/validations/validators/allow_blank_spec.rb +10 -0
  87. data/spec/grape/validations/validators/coerce_spec.rb +48 -18
  88. data/spec/grape/validations/validators/default_spec.rb +110 -20
  89. data/spec/grape/validations/validators/presence_spec.rb +41 -3
  90. data/spec/grape/validations/validators/regexp_spec.rb +7 -2
  91. data/spec/grape/validations_spec.rb +20 -1
  92. data/spec/support/file_streamer.rb +11 -0
  93. data/spec/support/versioned_helpers.rb +1 -1
  94. metadata +14 -2
@@ -4,7 +4,7 @@ describe Grape::Middleware::Formatter do
4
4
  subject { Grape::Middleware::Formatter.new(app) }
5
5
  before { allow(subject).to receive(:dup).and_return(subject) }
6
6
 
7
- let(:app) { lambda { |env| [200, {}, [@body || { 'foo' => 'bar' }]] } }
7
+ let(:app) { ->(_env) { [200, {}, [@body || { 'foo' => 'bar' }]] } }
8
8
 
9
9
  context 'serialization' do
10
10
  it 'looks at the bodies for possibly serializable data' do
@@ -21,7 +21,7 @@ describe Grape::Middleware::Formatter do
21
21
  end
22
22
  end
23
23
 
24
- subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').last.each { |b| expect(b).to eq('"bar"') }
24
+ subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') }
25
25
  end
26
26
 
27
27
  it 'calls #to_json if the content type is jsonapi' do
@@ -32,7 +32,7 @@ describe Grape::Middleware::Formatter do
32
32
  end
33
33
  end
34
34
 
35
- subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') }
35
+ subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') }
36
36
  end
37
37
 
38
38
  it 'calls #to_xml if the content type is xml' do
@@ -43,7 +43,7 @@ describe Grape::Middleware::Formatter do
43
43
  end
44
44
  end
45
45
 
46
- subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').last.each { |b| expect(b).to eq('<bar/>') }
46
+ subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('<bar/>') }
47
47
  end
48
48
  end
49
49
 
@@ -145,21 +145,21 @@ describe Grape::Middleware::Formatter do
145
145
 
146
146
  context 'content-type' do
147
147
  it 'is set for json' do
148
- _, headers, _ = subject.call('PATH_INFO' => '/info.json')
148
+ _, headers, = subject.call('PATH_INFO' => '/info.json')
149
149
  expect(headers['Content-type']).to eq('application/json')
150
150
  end
151
151
  it 'is set for xml' do
152
- _, headers, _ = subject.call('PATH_INFO' => '/info.xml')
152
+ _, headers, = subject.call('PATH_INFO' => '/info.xml')
153
153
  expect(headers['Content-type']).to eq('application/xml')
154
154
  end
155
155
  it 'is set for txt' do
156
- _, headers, _ = subject.call('PATH_INFO' => '/info.txt')
156
+ _, headers, = subject.call('PATH_INFO' => '/info.txt')
157
157
  expect(headers['Content-type']).to eq('text/plain')
158
158
  end
159
159
  it 'is set for custom' do
160
160
  subject.options[:content_types] = {}
161
161
  subject.options[:content_types][:custom] = 'application/x-custom'
162
- _, headers, _ = subject.call('PATH_INFO' => '/info.custom')
162
+ _, headers, = subject.call('PATH_INFO' => '/info.custom')
163
163
  expect(headers['Content-type']).to eq('application/x-custom')
164
164
  end
165
165
  end
@@ -168,7 +168,7 @@ describe Grape::Middleware::Formatter do
168
168
  it 'uses custom formatter' do
169
169
  subject.options[:content_types] = {}
170
170
  subject.options[:content_types][:custom] = "don't care"
171
- subject.options[:formatters][:custom] = lambda { |obj, env| 'CUSTOM FORMAT' }
171
+ subject.options[:formatters][:custom] = ->(_obj, _env) { 'CUSTOM FORMAT' }
172
172
  _, _, body = subject.call('PATH_INFO' => '/info.custom')
173
173
  expect(body.body).to eq(['CUSTOM FORMAT'])
174
174
  end
@@ -178,7 +178,7 @@ describe Grape::Middleware::Formatter do
178
178
  expect(body.body).to eq(['["blah"]'])
179
179
  end
180
180
  it 'uses custom json formatter' do
181
- subject.options[:formatters][:json] = lambda { |obj, env| 'CUSTOM JSON FORMAT' }
181
+ subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' }
182
182
  _, _, body = subject.call('PATH_INFO' => '/info.json')
183
183
  expect(body.body).to eq(['CUSTOM JSON FORMAT'])
184
184
  end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Globals do
4
+ subject { Grape::Middleware::Globals.new(blank_app) }
5
+ before { allow(subject).to receive(:dup).and_return(subject) }
6
+
7
+ let(:blank_app) { ->(_env) { [200, {}, 'Hi there.'] } }
8
+
9
+ it 'calls through to the app' do
10
+ expect(subject.call({})).to eq([200, {}, 'Hi there.'])
11
+ end
12
+
13
+ context 'environment' do
14
+ it 'should set the grape.request environment' do
15
+ subject.call({})
16
+ expect(subject.env['grape.request']).to be_a(Grape::Request)
17
+ end
18
+ it 'should set the grape.request.headers environment' do
19
+ subject.call({})
20
+ expect(subject.env['grape.request.headers']).to be_a(Hash)
21
+ end
22
+ it 'should set the grape.request.params environment' do
23
+ subject.call('QUERY_STRING' => 'test=1', 'rack.input' => StringIO.new)
24
+ expect(subject.env['grape.request.params']).to be_a(Hash)
25
+ end
26
+ end
27
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Versioner::AcceptVersionHeader do
4
- let(:app) { lambda { |env| [200, env, env] } }
4
+ let(:app) { ->(env) { [200, env, env] } }
5
5
  subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, @options || {}) }
6
6
 
7
7
  before do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Versioner::Header do
4
- let(:app) { lambda { |env| [200, env, env] } }
4
+ let(:app) { ->(env) { [200, env, env] } }
5
5
  subject { Grape::Middleware::Versioner::Header.new(app, @options || {}) }
6
6
 
7
7
  before do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Versioner::Param do
4
- let(:app) { lambda { |env| [200, env, env['api.version']] } }
4
+ let(:app) { ->(env) { [200, env, env['api.version']] } }
5
5
  subject { Grape::Middleware::Versioner::Param.new(app, @options || {}) }
6
6
 
7
7
  it 'sets the API version based on the default param (apiver)' do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Middleware::Versioner::Path do
4
- let(:app) { lambda { |env| [200, env, env['api.version']] } }
4
+ let(:app) { ->(env) { [200, env, env['api.version']] } }
5
5
  subject { Grape::Middleware::Versioner::Path.new(app, @options || {}) }
6
6
 
7
7
  it 'sets the API version based on the first path' do
@@ -182,11 +182,12 @@ module Grape
182
182
 
183
183
  describe '#suffix' do
184
184
  context 'when using a specific format' do
185
- it 'is empty' do
185
+ it 'accepts specified format' do
186
186
  path = Path.new(nil, nil, {})
187
187
  allow(path).to receive(:uses_specific_format?) { true }
188
+ allow(path).to receive(:settings) { { format: :json } }
188
189
 
189
- expect(path.suffix).to eql('')
190
+ expect(path.suffix).to eql('(.json)')
190
191
  end
191
192
  end
192
193
 
@@ -237,12 +238,13 @@ module Grape
237
238
  end
238
239
 
239
240
  context 'when using a specific format' do
240
- it 'does not have a suffix' do
241
+ it 'might have a suffix with specified format' do
241
242
  path = Path.new(nil, nil, {})
242
243
  allow(path).to receive(:path) { '/the/path' }
243
244
  allow(path).to receive(:uses_specific_format?) { true }
245
+ allow(path).to receive(:settings) { { format: :json } }
244
246
 
245
- expect(path.path_with_suffix).to eql('/the/path')
247
+ expect(path.path_with_suffix).to eql('/the/path(.json)')
246
248
  end
247
249
  end
248
250
  end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ module Grape
4
+ module Presenters
5
+ module InsideRouteSpec
6
+ class Dummy
7
+ include Grape::DSL::InsideRoute
8
+
9
+ attr_reader :env, :request, :new_settings
10
+
11
+ def initialize
12
+ @env = {}
13
+ @header = {}
14
+ @new_settings = { namespace_inheritable: {}, namespace_stackable: {} }
15
+ end
16
+ end
17
+ end
18
+
19
+ describe Presenter do
20
+ describe 'represent' do
21
+ let(:object_mock) do
22
+ Object.new
23
+ end
24
+
25
+ it 'represent object' do
26
+ expect(Presenter.represent(object_mock)).to eq object_mock
27
+ end
28
+ end
29
+
30
+ subject { InsideRouteSpec::Dummy.new }
31
+
32
+ describe 'present' do
33
+ let(:hash_mock) do
34
+ { key: :value }
35
+ end
36
+
37
+ describe 'instance' do
38
+ before do
39
+ subject.present hash_mock, with: Grape::Presenters::Presenter
40
+ end
41
+ it 'presents dummy hash' do
42
+ expect(subject.body).to eq hash_mock
43
+ end
44
+ end
45
+
46
+ describe 'multiple presenter' do
47
+ let(:hash_mock1) do
48
+ { key1: :value1 }
49
+ end
50
+
51
+ let(:hash_mock2) do
52
+ { key2: :value2 }
53
+ end
54
+
55
+ describe 'instance' do
56
+ before do
57
+ subject.present hash_mock1, with: Grape::Presenters::Presenter
58
+ subject.present hash_mock2, with: Grape::Presenters::Presenter
59
+ end
60
+
61
+ it 'presents both dummy presenter' do
62
+ expect(subject.body[:key1]).to eq hash_mock1[:key1]
63
+ expect(subject.body[:key2]).to eq hash_mock2[:key2]
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  module Grape
3
3
  module Util
4
4
  describe InheritableValues do
5
- let(:parent){ InheritableValues.new }
5
+ let(:parent) { InheritableValues.new }
6
6
  subject { InheritableValues.new(parent) }
7
7
 
8
8
  describe '#delete' do
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  module Grape
3
3
  module Util
4
4
  describe StackableValues do
5
- let(:parent){ StackableValues.new }
5
+ let(:parent) { StackableValues.new }
6
6
  subject { StackableValues.new(parent) }
7
7
 
8
8
  describe '#keys' do
@@ -31,7 +31,7 @@ module Grape
31
31
  config2: 'beta',
32
32
  config3: { config4: 'gamma' },
33
33
  config5: { config6: { config7: 7, config8: 8 } }
34
- )
34
+ )
35
35
  end
36
36
  end
37
37
  end
@@ -9,6 +9,70 @@ describe Grape::Validations::ParamsScope do
9
9
  subject
10
10
  end
11
11
 
12
+ context 'setting a default' do
13
+ let(:documentation) { subject.routes.first.route_params }
14
+
15
+ context 'when the default value is truthy' do
16
+ before do
17
+ subject.params do
18
+ optional :int, type: Integer, default: 42
19
+ end
20
+ subject.get
21
+ end
22
+
23
+ it 'adds documentation about the default value' do
24
+ expect(documentation).to have_key('int')
25
+ expect(documentation['int']).to have_key(:default)
26
+ expect(documentation['int'][:default]).to eq(42)
27
+ end
28
+ end
29
+
30
+ context 'when the default value is false' do
31
+ before do
32
+ subject.params do
33
+ optional :bool, type: Virtus::Attribute::Boolean, default: false
34
+ end
35
+ subject.get
36
+ end
37
+
38
+ it 'adds documentation about the default value' do
39
+ expect(documentation).to have_key('bool')
40
+ expect(documentation['bool']).to have_key(:default)
41
+ expect(documentation['bool'][:default]).to eq(false)
42
+ end
43
+ end
44
+
45
+ context 'when the default value is nil' do
46
+ before do
47
+ subject.params do
48
+ optional :object, type: Object, default: nil
49
+ end
50
+ subject.get
51
+ end
52
+
53
+ it 'adds documentation about the default value' do
54
+ expect(documentation).to have_key('object')
55
+ expect(documentation['object']).to have_key(:default)
56
+ expect(documentation['object'][:default]).to eq(nil)
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'without a default' do
62
+ before do
63
+ subject.params do
64
+ optional :object, type: Object
65
+ end
66
+ subject.get
67
+ end
68
+
69
+ it 'does not add documentation for the default value' do
70
+ documentation = subject.routes.first.route_params
71
+ expect(documentation).to have_key('object')
72
+ expect(documentation['object']).not_to have_key(:default)
73
+ end
74
+ end
75
+
12
76
  context 'setting description' do
13
77
  [:desc, :description].each do |description_type|
14
78
  it "allows setting #{description_type}" do
@@ -66,6 +66,11 @@ describe Grape::Validations::AllowBlankValidator do
66
66
  end
67
67
  get '/allow_boolean_blank'
68
68
 
69
+ params do
70
+ requires :val, type: Boolean, allow_blank: false
71
+ end
72
+ get '/disallow_boolean_blank'
73
+
69
74
  params do
70
75
  optional :user, type: Hash do
71
76
  requires :name, allow_blank: false
@@ -201,6 +206,11 @@ describe Grape::Validations::AllowBlankValidator do
201
206
  get '/allow_boolean_blank', val: ''
202
207
  expect(last_response.status).to eq(200)
203
208
  end
209
+
210
+ it 'accepts false when boolean allow_blank' do
211
+ get '/disallow_boolean_blank', val: false
212
+ expect(last_response.status).to eq(200)
213
+ end
204
214
  end
205
215
 
206
216
  context 'in an optional group' do
@@ -122,30 +122,60 @@ describe Grape::Validations::CoerceValidator do
122
122
  expect(last_response.body).to eq('Fixnum')
123
123
  end
124
124
 
125
- it 'Array of Integers' do
126
- subject.params do
127
- requires :arry, coerce: Array[Integer]
128
- end
129
- subject.get '/array' do
130
- params[:arry][0].class
125
+ context 'Array' do
126
+ it 'Array of Integers' do
127
+ subject.params do
128
+ requires :arry, coerce: Array[Integer]
129
+ end
130
+ subject.get '/array' do
131
+ params[:arry][0].class
132
+ end
133
+
134
+ get '/array', arry: %w(1 2 3)
135
+ expect(last_response.status).to eq(200)
136
+ expect(last_response.body).to eq('Fixnum')
131
137
  end
132
138
 
133
- get '/array', arry: %w(1 2 3)
134
- expect(last_response.status).to eq(200)
135
- expect(last_response.body).to eq('Fixnum')
136
- end
139
+ it 'Array of Bools' do
140
+ subject.params do
141
+ requires :arry, coerce: Array[Virtus::Attribute::Boolean]
142
+ end
143
+ subject.get '/array' do
144
+ params[:arry][0].class
145
+ end
137
146
 
138
- it 'Array of Bools' do
139
- subject.params do
140
- requires :arry, coerce: Array[Virtus::Attribute::Boolean]
147
+ get 'array', arry: [1, 0]
148
+ expect(last_response.status).to eq(200)
149
+ expect(last_response.body).to eq('TrueClass')
141
150
  end
142
- subject.get '/array' do
143
- params[:arry][0].class
151
+ end
152
+
153
+ context 'Set' do
154
+ it 'Set of Integers' do
155
+ subject.params do
156
+ requires :set, coerce: Set[Integer]
157
+ end
158
+ subject.get '/set' do
159
+ params[:set].first.class
160
+ end
161
+
162
+ get '/set', set: Set.new([1, 2, 3, 4]).to_a
163
+ expect(last_response.status).to eq(200)
164
+ expect(last_response.body).to eq('Fixnum')
144
165
  end
145
166
 
146
- get 'array', arry: [1, 0]
147
- expect(last_response.status).to eq(200)
148
- expect(last_response.body).to eq('TrueClass')
167
+ it 'Set of Bools' do
168
+ subject.params do
169
+ requires :set, coerce: Set[Virtus::Attribute::Boolean]
170
+ end
171
+ subject.get '/set' do
172
+ params[:set].first.class
173
+ end
174
+
175
+ get '/set', set: Set.new([1, 0]).to_a
176
+ expect(last_response.status).to eq(200)
177
+ expect(last_response.body).to eq('TrueClass')
178
+ end
149
179
  end
150
180
 
151
181
  it 'Bool' do