grape 1.2.4 → 1.2.5
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.
- 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
|