grape 1.2.4 → 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -4
- data/README.md +144 -4
- data/grape.gemspec +3 -1
- data/lib/grape.rb +94 -66
- data/lib/grape/api.rb +46 -4
- data/lib/grape/api/instance.rb +23 -12
- data/lib/grape/dsl/desc.rb +11 -2
- data/lib/grape/dsl/validations.rb +4 -3
- data/lib/grape/eager_load.rb +18 -0
- data/lib/grape/endpoint.rb +3 -3
- data/lib/grape/error_formatter.rb +1 -1
- data/lib/grape/exceptions/validation_errors.rb +4 -2
- data/lib/grape/formatter.rb +1 -1
- data/lib/grape/middleware/auth/base.rb +2 -4
- data/lib/grape/middleware/base.rb +2 -0
- data/lib/grape/middleware/helpers.rb +10 -0
- data/lib/grape/parser.rb +1 -1
- data/lib/grape/util/base_inheritable.rb +34 -0
- data/lib/grape/util/inheritable_values.rb +5 -25
- data/lib/grape/util/lazy_block.rb +25 -0
- data/lib/grape/util/lazy_value.rb +5 -0
- data/lib/grape/util/reverse_stackable_values.rb +7 -36
- data/lib/grape/util/stackable_values.rb +19 -22
- data/lib/grape/validations/attributes_iterator.rb +5 -3
- data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
- data/lib/grape/validations/params_scope.rb +12 -12
- data/lib/grape/validations/single_attribute_iterator.rb +13 -0
- data/lib/grape/validations/validator_factory.rb +6 -11
- data/lib/grape/validations/validators/all_or_none.rb +6 -13
- data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
- data/lib/grape/validations/validators/base.rb +11 -10
- data/lib/grape/validations/validators/coerce.rb +4 -0
- data/lib/grape/validations/validators/default.rb +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
- data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
- data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
- data/spec/grape/api_remount_spec.rb +158 -0
- data/spec/grape/api_spec.rb +72 -0
- data/spec/grape/endpoint_spec.rb +1 -1
- data/spec/grape/exceptions/base_spec.rb +4 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
- data/spec/grape/integration/rack_spec.rb +22 -6
- data/spec/grape/middleware/base_spec.rb +8 -0
- data/spec/grape/middleware/formatter_spec.rb +11 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
- data/spec/grape/validations/params_scope_spec.rb +13 -0
- data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
- data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
- data/spec/grape/validations/validators/coerce_spec.rb +6 -2
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
- data/spec/grape/validations_spec.rb +32 -20
- metadata +103 -115
- data/Appraisals +0 -28
- data/Dangerfile +0 -2
- data/Gemfile +0 -33
- data/Gemfile.lock +0 -231
- data/Guardfile +0 -10
- data/RELEASING.md +0 -111
- data/Rakefile +0 -25
- data/benchmark/simple.rb +0 -27
- data/benchmark/simple_with_type_coercer.rb +0 -22
- data/gemfiles/multi_json.gemfile +0 -35
- data/gemfiles/multi_xml.gemfile +0 -35
- data/gemfiles/rack_1.5.2.gemfile.lock +0 -232
- data/gemfiles/rack_edge.gemfile +0 -35
- data/gemfiles/rails_3.gemfile +0 -36
- data/gemfiles/rails_3.gemfile.lock +0 -288
- data/gemfiles/rails_4.gemfile +0 -35
- data/gemfiles/rails_4.gemfile.lock +0 -280
- data/gemfiles/rails_5.gemfile +0 -35
- data/gemfiles/rails_5.gemfile.lock +0 -312
- data/gemfiles/rails_edge.gemfile +0 -35
- data/pkg/grape-1.2.0.gem +0 -0
- data/pkg/grape-1.2.1.gem +0 -0
- data/pkg/grape-1.2.3.gem +0 -0
data/lib/grape/version.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::API::Instance do
|
4
|
+
describe 'boolean constant' do
|
5
|
+
module DefinesBooleanInstanceSpec
|
6
|
+
class API < Grape::API
|
7
|
+
params do
|
8
|
+
requires :message, type: Boolean
|
9
|
+
end
|
10
|
+
post :echo do
|
11
|
+
{ class: params[:message].class.name, value: params[:message] }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def app
|
17
|
+
DefinesBooleanInstanceSpec::API
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:expected_body) do
|
21
|
+
{ class: 'TrueClass', value: true }.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets Boolean as a Virtus::Attribute::Boolean' do
|
25
|
+
post '/echo?message=true'
|
26
|
+
expect(last_response.status).to eq(201)
|
27
|
+
expect(last_response.body).to eq expected_body
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'Params endpoint type' do
|
31
|
+
subject { DefinesBooleanInstanceSpec::API.new.router.map['POST'].first.options[:params]['message'][:type] }
|
32
|
+
it 'params type is a Virtus::Attribute::Boolean' do
|
33
|
+
is_expected.to eq 'Virtus::Attribute::Boolean'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -97,6 +97,96 @@ describe Grape::API do
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
context 'when using an expression derived from a configuration' do
|
101
|
+
subject(:a_remounted_api) do
|
102
|
+
Class.new(Grape::API) do
|
103
|
+
get(mounted { "api_name_#{configuration[:api_name]}" }) do
|
104
|
+
'success'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
before do
|
110
|
+
root_api.mount a_remounted_api, with: {
|
111
|
+
api_name: 'a_name'
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'mounts the endpoint with the name' do
|
116
|
+
get 'api_name_a_name'
|
117
|
+
expect(last_response.body).to eq 'success'
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'does not mount the endpoint with a null name' do
|
121
|
+
get 'api_name_'
|
122
|
+
expect(last_response.body).not_to eq 'success'
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'when the expression lives in a namespace' do
|
126
|
+
subject(:a_remounted_api) do
|
127
|
+
Class.new(Grape::API) do
|
128
|
+
namespace :base do
|
129
|
+
get(mounted { "api_name_#{configuration[:api_name]}" }) do
|
130
|
+
'success'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'mounts the endpoint with the name' do
|
137
|
+
get 'base/api_name_a_name'
|
138
|
+
expect(last_response.body).to eq 'success'
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'does not mount the endpoint with a null name' do
|
142
|
+
get 'base/api_name_'
|
143
|
+
expect(last_response.body).not_to eq 'success'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when executing a standard block within a `mounted` block with all dynamic params' do
|
149
|
+
subject(:a_remounted_api) do
|
150
|
+
Class.new(Grape::API) do
|
151
|
+
mounted do
|
152
|
+
desc configuration[:description] do
|
153
|
+
headers configuration[:headers]
|
154
|
+
end
|
155
|
+
get configuration[:endpoint] do
|
156
|
+
configuration[:response]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
let(:api_endpoint) { 'custom_endpoint' }
|
163
|
+
let(:api_response) { 'custom response' }
|
164
|
+
let(:endpoint_description) { 'this is a custom API' }
|
165
|
+
let(:headers) do
|
166
|
+
{
|
167
|
+
'XAuthToken' => {
|
168
|
+
'description' => 'Validates your identity',
|
169
|
+
'required' => true
|
170
|
+
}
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'mounts the API and obtains the description and headers definition' do
|
175
|
+
root_api.mount a_remounted_api, with: {
|
176
|
+
description: endpoint_description,
|
177
|
+
headers: headers,
|
178
|
+
endpoint: api_endpoint,
|
179
|
+
response: api_response
|
180
|
+
}
|
181
|
+
get api_endpoint
|
182
|
+
expect(last_response.body).to eq api_response
|
183
|
+
expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:description])
|
184
|
+
.to eq endpoint_description
|
185
|
+
expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:headers])
|
186
|
+
.to eq headers
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
100
190
|
context 'when executing a custom block on mount' do
|
101
191
|
subject(:a_remounted_api) do
|
102
192
|
Class.new(Grape::API) do
|
@@ -264,6 +354,74 @@ describe Grape::API do
|
|
264
354
|
end
|
265
355
|
end
|
266
356
|
|
357
|
+
context 'a very complex configuration example' do
|
358
|
+
before do
|
359
|
+
top_level_api = Class.new(Grape::API) do
|
360
|
+
remounted_api = Class.new(Grape::API) do
|
361
|
+
get configuration[:endpoint_name] do
|
362
|
+
configuration[:response]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
expression_namespace = mounted { configuration[:namespace].to_s * 2 }
|
367
|
+
given(mounted { configuration[:should_mount_expressed] != false }) do
|
368
|
+
namespace expression_namespace do
|
369
|
+
mount remounted_api, with: { endpoint_name: configuration[:endpoint_name], response: configuration[:endpoint_response] }
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
root_api.mount top_level_api, with: configuration_options
|
374
|
+
end
|
375
|
+
|
376
|
+
context 'when the namespace should be mounted' do
|
377
|
+
let(:configuration_options) do
|
378
|
+
{
|
379
|
+
should_mount_expressed: true,
|
380
|
+
namespace: 'bang',
|
381
|
+
endpoint_name: 'james',
|
382
|
+
endpoint_response: 'bond'
|
383
|
+
}
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'gets a response' do
|
387
|
+
get 'bangbang/james'
|
388
|
+
expect(last_response.body).to eq 'bond'
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
context 'when should be mounted is nil' do
|
393
|
+
let(:configuration_options) do
|
394
|
+
{
|
395
|
+
should_mount_expressed: nil,
|
396
|
+
namespace: 'bang',
|
397
|
+
endpoint_name: 'james',
|
398
|
+
endpoint_response: 'bond'
|
399
|
+
}
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'gets a response' do
|
403
|
+
get 'bangbang/james'
|
404
|
+
expect(last_response.body).to eq 'bond'
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
context 'when it should not be mounted' do
|
409
|
+
let(:configuration_options) do
|
410
|
+
{
|
411
|
+
should_mount_expressed: false,
|
412
|
+
namespace: 'bang',
|
413
|
+
endpoint_name: 'james',
|
414
|
+
endpoint_response: 'bond'
|
415
|
+
}
|
416
|
+
end
|
417
|
+
|
418
|
+
it 'gets a response' do
|
419
|
+
get 'bangbang/james'
|
420
|
+
expect(last_response.body).not_to eq 'bond'
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
267
425
|
context 'when the configuration is read in a helper' do
|
268
426
|
subject(:a_remounted_api) do
|
269
427
|
Class.new(Grape::API) do
|
data/spec/grape/api_spec.rb
CHANGED
@@ -885,6 +885,40 @@ XML
|
|
885
885
|
end
|
886
886
|
end
|
887
887
|
|
888
|
+
describe '.compile!' do
|
889
|
+
it 'requires the grape/eager_load file' do
|
890
|
+
expect(app).to receive(:require).with('grape/eager_load') { nil }
|
891
|
+
app.compile!
|
892
|
+
end
|
893
|
+
|
894
|
+
it 'compiles the instance for rack!' do
|
895
|
+
stubbed_object = double(:instance_for_rack)
|
896
|
+
allow(app).to receive(:instance_for_rack) { stubbed_object }
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
# NOTE: this method is required to preserve the ability of pre-mounting
|
901
|
+
# the root API into a namespace, it may be deprecated in the future.
|
902
|
+
describe 'instance_for_rack' do
|
903
|
+
context 'when the app was not mounted' do
|
904
|
+
it 'returns the base_instance' do
|
905
|
+
expect(app.send(:instance_for_rack)).to eq app.base_instance
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
context 'when the app was mounted' do
|
910
|
+
it 'returns the first mounted instance' do
|
911
|
+
mounted_app = app
|
912
|
+
Class.new(Grape::API) do
|
913
|
+
namespace 'new_namespace' do
|
914
|
+
mount mounted_app
|
915
|
+
end
|
916
|
+
end
|
917
|
+
expect(app.send(:instance_for_rack)).to eq app.send(:mounted_instances).first
|
918
|
+
end
|
919
|
+
end
|
920
|
+
end
|
921
|
+
|
888
922
|
describe 'filters' do
|
889
923
|
it 'adds a before filter' do
|
890
924
|
subject.before { @foo = 'first' }
|
@@ -3720,6 +3754,44 @@ XML
|
|
3720
3754
|
end
|
3721
3755
|
end
|
3722
3756
|
|
3757
|
+
describe '.configure' do
|
3758
|
+
context 'when given a block' do
|
3759
|
+
it 'returns self' do
|
3760
|
+
expect(subject.configure {}).to be subject
|
3761
|
+
end
|
3762
|
+
|
3763
|
+
it 'calls the block passing the config' do
|
3764
|
+
call = [false, nil]
|
3765
|
+
subject.configure do |config|
|
3766
|
+
call = [true, config]
|
3767
|
+
end
|
3768
|
+
|
3769
|
+
expect(call[0]).to be true
|
3770
|
+
expect(call[1]).not_to be_nil
|
3771
|
+
end
|
3772
|
+
end
|
3773
|
+
|
3774
|
+
context 'when not given a block' do
|
3775
|
+
it 'returns a configuration object' do
|
3776
|
+
expect(subject.configure).to respond_to(:[], :[]=)
|
3777
|
+
end
|
3778
|
+
end
|
3779
|
+
|
3780
|
+
it 'allows configuring the api' do
|
3781
|
+
subject.configure do |config|
|
3782
|
+
config[:hello] = 'hello'
|
3783
|
+
config[:bread] = 'bread'
|
3784
|
+
end
|
3785
|
+
|
3786
|
+
subject.get '/hello-bread' do
|
3787
|
+
"#{configuration[:hello]} #{configuration[:bread]}"
|
3788
|
+
end
|
3789
|
+
|
3790
|
+
get '/hello-bread'
|
3791
|
+
expect(last_response.body).to eq 'hello bread'
|
3792
|
+
end
|
3793
|
+
end
|
3794
|
+
|
3723
3795
|
context 'catch-all' do
|
3724
3796
|
before do
|
3725
3797
|
api1 = Class.new(Grape::API)
|
data/spec/grape/endpoint_spec.rb
CHANGED
@@ -8,8 +8,11 @@ describe Grape::Exceptions::Base do
|
|
8
8
|
let(:attributes) { { klass: String, to_format: 'xml' } }
|
9
9
|
|
10
10
|
after do
|
11
|
+
I18n.enforce_available_locales = true
|
11
12
|
I18n.available_locales = %i[en]
|
13
|
+
I18n.locale = :en
|
12
14
|
I18n.default_locale = :en
|
15
|
+
I18n.reload!
|
13
16
|
end
|
14
17
|
|
15
18
|
context 'when I18n enforces available locales' do
|
@@ -29,6 +32,7 @@ describe Grape::Exceptions::Base do
|
|
29
32
|
context 'when the fallback locale is not available' do
|
30
33
|
before do
|
31
34
|
I18n.available_locales = %i[de jp]
|
35
|
+
I18n.locale = :de
|
32
36
|
I18n.default_locale = :de
|
33
37
|
end
|
34
38
|
|
@@ -77,10 +77,12 @@ describe Grape::Exceptions::ValidationErrors do
|
|
77
77
|
end
|
78
78
|
get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
|
79
79
|
expect(last_response.status).to eq(400)
|
80
|
-
expect(JSON.parse(last_response.body)).to eq(
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
expect(JSON.parse(last_response.body)).to eq(
|
81
|
+
[
|
82
|
+
'params' => %w[beer wine juice],
|
83
|
+
'messages' => ['are missing, exactly one parameter must be provided']
|
84
|
+
]
|
85
|
+
)
|
84
86
|
end
|
85
87
|
end
|
86
88
|
end
|
@@ -19,16 +19,32 @@ describe Rack do
|
|
19
19
|
}
|
20
20
|
env = Rack::MockRequest.env_for('/', options)
|
21
21
|
|
22
|
-
unless RUBY_PLATFORM == 'java'
|
23
|
-
major, minor, patch = Rack.release.split('.').map(&:to_i)
|
24
|
-
patch ||= 0 # rack <= 1.5.2 does not specify patch version
|
25
|
-
pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 3) || (minor >= 6)))
|
26
|
-
end
|
27
|
-
|
28
22
|
expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test')
|
29
23
|
ensure
|
30
24
|
input.close
|
31
25
|
input.unlink
|
32
26
|
end
|
33
27
|
end
|
28
|
+
|
29
|
+
context 'when the app is mounted' do
|
30
|
+
def app
|
31
|
+
@main_app ||= Class.new(Grape::API) do
|
32
|
+
get 'ping'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
let!(:base) do
|
37
|
+
app_to_mount = app
|
38
|
+
Class.new(Grape::API) do
|
39
|
+
namespace 'namespace' do
|
40
|
+
mount app_to_mount
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'finds the app on the namespace' do
|
46
|
+
get '/namespace/ping'
|
47
|
+
expect(last_response.status).to eq 200
|
48
|
+
end
|
49
|
+
end
|
34
50
|
end
|
@@ -114,6 +114,14 @@ describe Grape::Middleware::Base do
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
describe '#context' do
|
118
|
+
subject { Grape::Middleware::Base.new(blank_app) }
|
119
|
+
it 'allows access to response context' do
|
120
|
+
subject.call(Grape::Env::API_ENDPOINT => { header: 'some header' })
|
121
|
+
expect(subject.context).to eq(header: 'some header')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
117
125
|
context 'options' do
|
118
126
|
it 'persists options passed at initialization' do
|
119
127
|
expect(Grape::Middleware::Base.new(blank_app, abc: true).options[:abc]).to be true
|
@@ -213,7 +213,13 @@ describe Grape::Middleware::Formatter do
|
|
213
213
|
context 'no content responses' do
|
214
214
|
let(:no_content_response) { ->(status) { [status, {}, ['']] } }
|
215
215
|
|
216
|
-
|
216
|
+
STATUSES_WITHOUT_BODY = if Gem::Version.new(Rack.release) >= Gem::Version.new('2.1.0')
|
217
|
+
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.keys
|
218
|
+
else
|
219
|
+
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY
|
220
|
+
end
|
221
|
+
|
222
|
+
STATUSES_WITHOUT_BODY.each do |status|
|
217
223
|
it "does not modify a #{status} response" do
|
218
224
|
expected_response = no_content_response[status]
|
219
225
|
allow(app).to receive(:call).and_return(expected_response)
|
@@ -391,6 +397,10 @@ describe Grape::Middleware::Formatter do
|
|
391
397
|
Grape::Formatter.register :invalid, InvalidFormatter
|
392
398
|
Grape::ContentTypes::CONTENT_TYPES[:invalid] = 'application/x-invalid'
|
393
399
|
end
|
400
|
+
after do
|
401
|
+
Grape::ContentTypes::CONTENT_TYPES.delete(:invalid)
|
402
|
+
Grape::Formatter.default_elements.delete(:invalid)
|
403
|
+
end
|
394
404
|
|
395
405
|
it 'returns response by invalid formatter' do
|
396
406
|
env = { 'PATH_INFO' => '/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid' }
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Validations::MultipleAttributesIterator do
|
4
|
+
describe '#each' do
|
5
|
+
subject(:iterator) { described_class.new(validator, scope, params) }
|
6
|
+
let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) }
|
7
|
+
let(:validator) { double(attrs: %i[first second third]) }
|
8
|
+
|
9
|
+
context 'when params is a hash' do
|
10
|
+
let(:params) do
|
11
|
+
{ first: 'string', second: 'string' }
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'yields the whole params hash without the list of attrs' do
|
15
|
+
expect { |b| iterator.each(&b) }.to yield_with_args(params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when params is an array' do
|
20
|
+
let(:params) do
|
21
|
+
[{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }]
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'yields each element of the array without the list of attrs' do
|
25
|
+
expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|