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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +23 -80
- data/.travis.yml +1 -1
- data/CHANGELOG.md +27 -0
- data/Gemfile +1 -1
- data/Guardfile +1 -1
- data/LICENSE +1 -1
- data/README.md +131 -30
- data/Rakefile +1 -1
- data/UPGRADING.md +110 -1
- data/gemfiles/rails_3.gemfile +1 -1
- data/gemfiles/rails_4.gemfile +1 -1
- data/grape.gemspec +4 -4
- data/lib/grape.rb +92 -62
- data/lib/grape/api.rb +10 -10
- data/lib/grape/cookies.rb +1 -1
- data/lib/grape/dsl/configuration.rb +7 -7
- data/lib/grape/dsl/helpers.rb +3 -3
- data/lib/grape/dsl/inside_route.rb +50 -21
- data/lib/grape/dsl/parameters.rb +25 -6
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/dsl/routing.rb +11 -10
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/endpoint.rb +21 -19
- data/lib/grape/error_formatter/json.rb +1 -1
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/exceptions/validation.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/base.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -1
- data/lib/grape/formatter/serializable_hash.rb +4 -4
- data/lib/grape/formatter/txt.rb +1 -1
- data/lib/grape/formatter/xml.rb +1 -1
- data/lib/grape/http/headers.rb +27 -0
- data/lib/grape/http/request.rb +1 -1
- data/lib/grape/middleware/error.rb +10 -4
- data/lib/grape/middleware/formatter.rb +13 -9
- data/lib/grape/middleware/globals.rb +2 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +2 -1
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +3 -3
- data/lib/grape/presenters/presenter.rb +9 -0
- data/lib/grape/validations/params_scope.rb +3 -3
- data/lib/grape/validations/validators/allow_blank.rb +1 -1
- data/lib/grape/validations/validators/coerce.rb +6 -5
- data/lib/grape/validations/validators/default.rb +2 -2
- data/lib/grape/validations/validators/multiple_params_base.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/custom_validations_spec.rb +47 -0
- data/spec/grape/api/deeply_included_options_spec.rb +56 -0
- data/spec/grape/api_spec.rb +64 -42
- data/spec/grape/dsl/configuration_spec.rb +2 -2
- data/spec/grape/dsl/helpers_spec.rb +1 -1
- data/spec/grape/dsl/inside_route_spec.rb +75 -19
- data/spec/grape/dsl/parameters_spec.rb +59 -10
- data/spec/grape/dsl/request_response_spec.rb +62 -2
- data/spec/grape/dsl/routing_spec.rb +116 -18
- data/spec/grape/endpoint_spec.rb +57 -5
- data/spec/grape/entity_spec.rb +1 -1
- data/spec/grape/exceptions/body_parse_errors_spec.rb +5 -5
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +32 -32
- data/spec/grape/exceptions/validation_errors_spec.rb +1 -1
- data/spec/grape/integration/rack_spec.rb +4 -3
- data/spec/grape/middleware/auth/strategies_spec.rb +2 -2
- data/spec/grape/middleware/base_spec.rb +2 -2
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/exception_spec.rb +5 -5
- data/spec/grape/middleware/formatter_spec.rb +10 -10
- data/spec/grape/middleware/globals_spec.rb +27 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/param_spec.rb +1 -1
- data/spec/grape/middleware/versioner/path_spec.rb +1 -1
- data/spec/grape/path_spec.rb +6 -4
- data/spec/grape/presenters/presenter_spec.rb +70 -0
- data/spec/grape/util/inheritable_values_spec.rb +1 -1
- data/spec/grape/util/stackable_values_spec.rb +1 -1
- data/spec/grape/util/strict_hash_configuration_spec.rb +1 -1
- data/spec/grape/validations/params_scope_spec.rb +64 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +10 -0
- data/spec/grape/validations/validators/coerce_spec.rb +48 -18
- data/spec/grape/validations/validators/default_spec.rb +110 -20
- data/spec/grape/validations/validators/presence_spec.rb +41 -3
- data/spec/grape/validations/validators/regexp_spec.rb +7 -2
- data/spec/grape/validations_spec.rb +20 -1
- data/spec/support/file_streamer.rb +11 -0
- data/spec/support/versioned_helpers.rb +1 -1
- 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) {
|
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,
|
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,
|
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,
|
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,
|
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] =
|
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] =
|
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) {
|
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::Param do
|
4
|
-
let(:app) {
|
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) {
|
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
|
data/spec/grape/path_spec.rb
CHANGED
@@ -182,11 +182,12 @@ module Grape
|
|
182
182
|
|
183
183
|
describe '#suffix' do
|
184
184
|
context 'when using a specific format' do
|
185
|
-
it '
|
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 '
|
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
|
@@ -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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
143
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
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
|