grape 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/README.md +43 -10
- data/UPGRADING.md +91 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +7 -14
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +18 -9
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +7 -6
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +21 -36
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +10 -16
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +3 -0
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +92 -58
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +1 -0
- data/lib/grape/validations/validators/as.rb +4 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
- data/lib/grape/validations/validators/base.rb +12 -7
- data/lib/grape/validations/validators/coerce.rb +8 -9
- data/lib/grape/validations/validators/default.rb +1 -0
- data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
- data/lib/grape/validations/validators/presence.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -0
- data/lib/grape/validations/validators/same_as.rb +1 -0
- data/lib/grape/validations/validators/values.rb +3 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +3 -1
- data/spec/grape/api/custom_validations_spec.rb +1 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +203 -37
- data/spec/grape/dsl/callbacks_spec.rb +1 -1
- data/spec/grape/dsl/middleware_spec.rb +1 -1
- data/spec/grape/dsl/parameters_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/endpoint/declared_spec.rb +259 -1
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/entity_spec.rb +10 -10
- data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +4 -3
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +129 -22
- data/spec/grape/validations/validators/except_values_spec.rb +2 -2
- data/spec/grape/validations/validators/values_spec.rb +15 -11
- data/spec/grape/validations_spec.rb +280 -0
- data/spec/shared/versioning_examples.rb +22 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +1 -1
- metadata +8 -6
@@ -13,6 +13,7 @@ module Grape
|
|
13
13
|
'Use the except validator instead.' if @excepts
|
14
14
|
|
15
15
|
raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc)
|
16
|
+
|
16
17
|
warn '[DEPRECATION] The values validator proc option is deprecated. ' \
|
17
18
|
'The lambda expression can now be assigned directly to values.' if @proc
|
18
19
|
else
|
@@ -51,6 +52,7 @@ module Grape
|
|
51
52
|
def check_values(param_array, attr_name)
|
52
53
|
values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
|
53
54
|
return true if values.nil?
|
55
|
+
|
54
56
|
begin
|
55
57
|
return param_array.all? { |param| values.call(param) } if values.is_a? Proc
|
56
58
|
rescue StandardError => e
|
@@ -63,6 +65,7 @@ module Grape
|
|
63
65
|
def check_excepts(param_array)
|
64
66
|
excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
|
65
67
|
return true if excepts.nil?
|
68
|
+
|
66
69
|
param_array.none? { |param| excepts.include?(param) }
|
67
70
|
end
|
68
71
|
|
data/lib/grape/version.rb
CHANGED
data/lib/grape.rb
CHANGED
@@ -12,6 +12,7 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
12
12
|
require 'active_support/core_ext/object/blank'
|
13
13
|
require 'active_support/core_ext/array/extract_options'
|
14
14
|
require 'active_support/core_ext/array/wrap'
|
15
|
+
require 'active_support/core_ext/array/conversions'
|
15
16
|
require 'active_support/core_ext/hash/deep_merge'
|
16
17
|
require 'active_support/core_ext/hash/reverse_merge'
|
17
18
|
require 'active_support/core_ext/hash/except'
|
@@ -21,7 +22,7 @@ require 'active_support/dependencies/autoload'
|
|
21
22
|
require 'active_support/notifications'
|
22
23
|
require 'i18n'
|
23
24
|
|
24
|
-
I18n.load_path << File.expand_path('
|
25
|
+
I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
|
25
26
|
|
26
27
|
module Grape
|
27
28
|
extend ::ActiveSupport::Autoload
|
@@ -75,6 +76,7 @@ module Grape
|
|
75
76
|
autoload :InvalidVersionHeader
|
76
77
|
autoload :MethodNotAllowed
|
77
78
|
autoload :InvalidResponse
|
79
|
+
autoload :EmptyMessageBody
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
@@ -10,6 +10,7 @@ describe Grape::Validations do
|
|
10
10
|
def validate_param!(attr_name, params)
|
11
11
|
@option = params[:max].to_i if params.key?(:max)
|
12
12
|
return if params[attr_name].length <= @option
|
13
|
+
|
13
14
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long")
|
14
15
|
end
|
15
16
|
end
|
@@ -11,7 +11,7 @@ describe Grape::Endpoint do
|
|
11
11
|
|
12
12
|
context 'get' do
|
13
13
|
it 'routes to a namespace param with dots' do
|
14
|
-
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[
|
14
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
|
15
15
|
get '/' do
|
16
16
|
params[:ns_with_dots]
|
17
17
|
end
|
@@ -23,8 +23,8 @@ describe Grape::Endpoint do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'routes to a path with multiple params with dots' do
|
26
|
-
subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[
|
27
|
-
another_id_with_dots: %r{[
|
26
|
+
subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^/]+},
|
27
|
+
another_id_with_dots: %r{[^/]+} } do
|
28
28
|
"#{params[:id_with_dots]}/#{params[:another_id_with_dots]}"
|
29
29
|
end
|
30
30
|
|
@@ -34,9 +34,9 @@ describe Grape::Endpoint do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'routes to namespace and path params with dots, with overridden requirements' do
|
37
|
-
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[
|
38
|
-
get ':another_id_with_dots', requirements: { ns_with_dots: %r{[
|
39
|
-
another_id_with_dots: %r{[
|
37
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
|
38
|
+
get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^/]+},
|
39
|
+
another_id_with_dots: %r{[^/]+} } do
|
40
40
|
"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
|
41
41
|
end
|
42
42
|
end
|
@@ -47,8 +47,8 @@ describe Grape::Endpoint do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'routes to namespace and path params with dots, with merged requirements' do
|
50
|
-
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[
|
51
|
-
get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[
|
50
|
+
subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
|
51
|
+
get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^/]+} } do
|
52
52
|
"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
|
53
53
|
end
|
54
54
|
end
|
@@ -340,19 +340,24 @@ describe Grape::API do
|
|
340
340
|
context 'when the configuration is read within a namespace' do
|
341
341
|
before do
|
342
342
|
a_remounted_api.namespace 'api' do
|
343
|
+
params do
|
344
|
+
requires configuration[:required_param]
|
345
|
+
end
|
343
346
|
get "/#{configuration[:path]}" do
|
344
347
|
'10 votes'
|
345
348
|
end
|
346
349
|
end
|
347
|
-
root_api.mount a_remounted_api, with: { path: 'votes' }
|
348
|
-
root_api.mount a_remounted_api, with: { path: 'scores' }
|
350
|
+
root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }
|
351
|
+
root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }
|
349
352
|
end
|
350
353
|
|
351
354
|
it 'will use the dynamic configuration on all routes' do
|
352
|
-
get 'api/votes'
|
355
|
+
get 'api/votes', param_key: 'a'
|
353
356
|
expect(last_response.body).to eql '10 votes'
|
354
|
-
get 'api/scores'
|
357
|
+
get 'api/scores', param_key: 'a'
|
355
358
|
expect(last_response.body).to eql '10 votes'
|
359
|
+
get 'api/votes'
|
360
|
+
expect(last_response.status).to eq 400
|
356
361
|
end
|
357
362
|
end
|
358
363
|
|
data/spec/grape/api_spec.rb
CHANGED
@@ -610,6 +610,7 @@ describe Grape::API do
|
|
610
610
|
subject.namespace :example do
|
611
611
|
before do
|
612
612
|
raise 'before filter ran twice' if already_run
|
613
|
+
|
613
614
|
already_run = true
|
614
615
|
header 'X-Custom-Header', 'foo'
|
615
616
|
end
|
@@ -650,12 +651,12 @@ describe Grape::API do
|
|
650
651
|
|
651
652
|
put '/example'
|
652
653
|
expect(last_response.status).to eql 405
|
653
|
-
expect(last_response.body).to eq
|
654
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
655
|
-
<error>
|
656
|
-
|
657
|
-
</error>
|
658
|
-
XML
|
654
|
+
expect(last_response.body).to eq <<~XML
|
655
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
656
|
+
<error>
|
657
|
+
<message>405 Not Allowed</message>
|
658
|
+
</error>
|
659
|
+
XML
|
659
660
|
end
|
660
661
|
end
|
661
662
|
|
@@ -749,6 +750,47 @@ XML
|
|
749
750
|
end
|
750
751
|
end
|
751
752
|
|
753
|
+
describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
|
754
|
+
before do
|
755
|
+
subject.namespace :example do
|
756
|
+
get do
|
757
|
+
'example'
|
758
|
+
end
|
759
|
+
|
760
|
+
patch do
|
761
|
+
'example'
|
762
|
+
end
|
763
|
+
|
764
|
+
post do
|
765
|
+
'example'
|
766
|
+
end
|
767
|
+
|
768
|
+
delete do
|
769
|
+
'example'
|
770
|
+
end
|
771
|
+
|
772
|
+
put do
|
773
|
+
'example'
|
774
|
+
end
|
775
|
+
end
|
776
|
+
options '/example'
|
777
|
+
end
|
778
|
+
|
779
|
+
describe 'it adds an OPTIONS route for namespaced endpoints that' do
|
780
|
+
it 'returns a 204' do
|
781
|
+
expect(last_response.status).to eql 204
|
782
|
+
end
|
783
|
+
|
784
|
+
it 'has an empty body' do
|
785
|
+
expect(last_response.body).to be_blank
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'has an Allow header' do
|
789
|
+
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
|
790
|
+
end
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
752
794
|
describe 'adds an OPTIONS route for namespaced endpoints that' do
|
753
795
|
before do
|
754
796
|
subject.before { header 'X-Custom-Header', 'foo' }
|
@@ -2106,7 +2148,9 @@ XML
|
|
2106
2148
|
context 'custom errors' do
|
2107
2149
|
before do
|
2108
2150
|
class ConnectionError < RuntimeError; end
|
2151
|
+
|
2109
2152
|
class DatabaseError < RuntimeError; end
|
2153
|
+
|
2110
2154
|
class CommunicationError < StandardError; end
|
2111
2155
|
end
|
2112
2156
|
|
@@ -2262,6 +2306,7 @@ XML
|
|
2262
2306
|
module ApiSpec
|
2263
2307
|
module APIErrors
|
2264
2308
|
class ParentError < StandardError; end
|
2309
|
+
|
2265
2310
|
class ChildError < ParentError; end
|
2266
2311
|
end
|
2267
2312
|
end
|
@@ -3119,10 +3164,10 @@ XML
|
|
3119
3164
|
subject.get 'method'
|
3120
3165
|
|
3121
3166
|
expect(subject.routes.map(&:params)).to eq [{
|
3122
|
-
'group1'
|
3167
|
+
'group1' => { required: true, type: 'Array' },
|
3123
3168
|
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
3124
3169
|
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
3125
|
-
'group2'
|
3170
|
+
'group2' => { required: true, type: 'Array' },
|
3126
3171
|
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
3127
3172
|
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
3128
3173
|
}]
|
@@ -3335,8 +3380,8 @@ XML
|
|
3335
3380
|
mount app
|
3336
3381
|
end
|
3337
3382
|
expect(subject.routes.size).to eq(2)
|
3338
|
-
expect(subject.routes.first.path).to match(%r{
|
3339
|
-
expect(subject.routes.last.path).to match(%r{
|
3383
|
+
expect(subject.routes.first.path).to match(%r{/cool/awesome})
|
3384
|
+
expect(subject.routes.last.path).to match(%r{/cool/sauce})
|
3340
3385
|
end
|
3341
3386
|
|
3342
3387
|
it 'mounts on a path' do
|
@@ -3358,7 +3403,7 @@ XML
|
|
3358
3403
|
APP2.get '/nice' do
|
3359
3404
|
'play'
|
3360
3405
|
end
|
3361
|
-
#
|
3406
|
+
# NOTE: that the reverse won't work, mount from outside-in
|
3362
3407
|
APP3 = subject
|
3363
3408
|
APP3.mount APP1 => '/app1'
|
3364
3409
|
APP1.mount APP2 => '/app2'
|
@@ -3549,6 +3594,7 @@ XML
|
|
3549
3594
|
def self.included(base)
|
3550
3595
|
base.extend(ClassMethods)
|
3551
3596
|
end
|
3597
|
+
|
3552
3598
|
module ClassMethods
|
3553
3599
|
def my_method
|
3554
3600
|
@test = true
|
@@ -3793,12 +3839,12 @@ XML
|
|
3793
3839
|
end
|
3794
3840
|
get '/example'
|
3795
3841
|
expect(last_response.status).to eq(500)
|
3796
|
-
expect(last_response.body).to eq
|
3797
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3798
|
-
<error>
|
3799
|
-
|
3800
|
-
</error>
|
3801
|
-
XML
|
3842
|
+
expect(last_response.body).to eq <<~XML
|
3843
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3844
|
+
<error>
|
3845
|
+
<message>cannot convert String to xml</message>
|
3846
|
+
</error>
|
3847
|
+
XML
|
3802
3848
|
end
|
3803
3849
|
it 'hash' do
|
3804
3850
|
subject.get '/example' do
|
@@ -3809,13 +3855,13 @@ XML
|
|
3809
3855
|
end
|
3810
3856
|
get '/example'
|
3811
3857
|
expect(last_response.status).to eq(200)
|
3812
|
-
expect(last_response.body).to eq
|
3813
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3814
|
-
<hash>
|
3815
|
-
|
3816
|
-
|
3817
|
-
</hash>
|
3818
|
-
XML
|
3858
|
+
expect(last_response.body).to eq <<~XML
|
3859
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3860
|
+
<hash>
|
3861
|
+
<example1>example1</example1>
|
3862
|
+
<example2>example2</example2>
|
3863
|
+
</hash>
|
3864
|
+
XML
|
3819
3865
|
end
|
3820
3866
|
it 'array' do
|
3821
3867
|
subject.get '/example' do
|
@@ -3823,13 +3869,13 @@ XML
|
|
3823
3869
|
end
|
3824
3870
|
get '/example'
|
3825
3871
|
expect(last_response.status).to eq(200)
|
3826
|
-
expect(last_response.body).to eq
|
3827
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3828
|
-
<strings type="array">
|
3829
|
-
|
3830
|
-
|
3831
|
-
</strings>
|
3832
|
-
XML
|
3872
|
+
expect(last_response.body).to eq <<~XML
|
3873
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3874
|
+
<strings type="array">
|
3875
|
+
<string>example1</string>
|
3876
|
+
<string>example2</string>
|
3877
|
+
</strings>
|
3878
|
+
XML
|
3833
3879
|
end
|
3834
3880
|
it 'raised :error from middleware' do
|
3835
3881
|
middleware = Class.new(Grape::Middleware::Base) do
|
@@ -3842,12 +3888,12 @@ XML
|
|
3842
3888
|
end
|
3843
3889
|
get '/'
|
3844
3890
|
expect(last_response.status).to eq(42)
|
3845
|
-
expect(last_response.body).to eq
|
3846
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3847
|
-
<error>
|
3848
|
-
|
3849
|
-
</error>
|
3850
|
-
XML
|
3891
|
+
expect(last_response.body).to eq <<~XML
|
3892
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3893
|
+
<error>
|
3894
|
+
<message>Unauthorized</message>
|
3895
|
+
</error>
|
3896
|
+
XML
|
3851
3897
|
end
|
3852
3898
|
end
|
3853
3899
|
end
|
@@ -4040,6 +4086,49 @@ XML
|
|
4040
4086
|
end
|
4041
4087
|
end
|
4042
4088
|
|
4089
|
+
describe '.inherited' do
|
4090
|
+
context 'overriding within class' do
|
4091
|
+
let(:root_api) do
|
4092
|
+
Class.new(Grape::API) do
|
4093
|
+
@bar = 'Hello, world'
|
4094
|
+
|
4095
|
+
def self.inherited(child_api)
|
4096
|
+
super
|
4097
|
+
child_api.instance_variable_set(:@foo, @bar.dup)
|
4098
|
+
end
|
4099
|
+
end
|
4100
|
+
end
|
4101
|
+
|
4102
|
+
let(:child_api) { Class.new(root_api) }
|
4103
|
+
|
4104
|
+
it 'allows overriding the hook' do
|
4105
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4106
|
+
end
|
4107
|
+
end
|
4108
|
+
|
4109
|
+
context 'overriding via composition' do
|
4110
|
+
module Inherited
|
4111
|
+
def inherited(api)
|
4112
|
+
super
|
4113
|
+
api.instance_variable_set(:@foo, @bar.dup)
|
4114
|
+
end
|
4115
|
+
end
|
4116
|
+
|
4117
|
+
let(:root_api) do
|
4118
|
+
Class.new(Grape::API) do
|
4119
|
+
@bar = 'Hello, world'
|
4120
|
+
extend Inherited
|
4121
|
+
end
|
4122
|
+
end
|
4123
|
+
|
4124
|
+
let(:child_api) { Class.new(root_api) }
|
4125
|
+
|
4126
|
+
it 'allows overriding the hook' do
|
4127
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4128
|
+
end
|
4129
|
+
end
|
4130
|
+
end
|
4131
|
+
|
4043
4132
|
describe 'const_missing' do
|
4044
4133
|
subject(:grape_api) { Class.new(Grape::API) }
|
4045
4134
|
let(:mounted) do
|
@@ -4056,4 +4145,81 @@ XML
|
|
4056
4145
|
expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
|
4057
4146
|
end
|
4058
4147
|
end
|
4148
|
+
|
4149
|
+
describe 'custom route helpers on nested APIs' do
|
4150
|
+
let(:shared_api_module) do
|
4151
|
+
Module.new do
|
4152
|
+
# rubocop:disable Style/ExplicitBlockArgument because this causes
|
4153
|
+
# the underlying issue in this form
|
4154
|
+
def uniqe_id_route
|
4155
|
+
params do
|
4156
|
+
use :unique_id
|
4157
|
+
end
|
4158
|
+
route_param(:id) do
|
4159
|
+
yield
|
4160
|
+
end
|
4161
|
+
end
|
4162
|
+
# rubocop:enable Style/ExplicitBlockArgument
|
4163
|
+
end
|
4164
|
+
end
|
4165
|
+
let(:shared_api_definitions) do
|
4166
|
+
Module.new do
|
4167
|
+
extend ActiveSupport::Concern
|
4168
|
+
|
4169
|
+
included do
|
4170
|
+
helpers do
|
4171
|
+
params :unique_id do
|
4172
|
+
requires :id, type: String,
|
4173
|
+
allow_blank: false,
|
4174
|
+
regexp: /\d+-\d+/
|
4175
|
+
end
|
4176
|
+
end
|
4177
|
+
end
|
4178
|
+
end
|
4179
|
+
end
|
4180
|
+
let(:orders_root) do
|
4181
|
+
shared = shared_api_definitions
|
4182
|
+
find = orders_find_endpoint
|
4183
|
+
Class.new(Grape::API) do
|
4184
|
+
include shared
|
4185
|
+
|
4186
|
+
namespace(:orders) do
|
4187
|
+
mount find
|
4188
|
+
end
|
4189
|
+
end
|
4190
|
+
end
|
4191
|
+
let(:orders_find_endpoint) do
|
4192
|
+
shared = shared_api_definitions
|
4193
|
+
Class.new(Grape::API) do
|
4194
|
+
include shared
|
4195
|
+
|
4196
|
+
uniqe_id_route do
|
4197
|
+
desc 'Fetch a single order' do
|
4198
|
+
detail 'While specifying the order id on the route'
|
4199
|
+
end
|
4200
|
+
get { params[:id] }
|
4201
|
+
end
|
4202
|
+
end
|
4203
|
+
end
|
4204
|
+
subject(:grape_api) do
|
4205
|
+
Class.new(Grape::API) do
|
4206
|
+
version 'v1', using: :path
|
4207
|
+
end
|
4208
|
+
end
|
4209
|
+
|
4210
|
+
before do
|
4211
|
+
Grape::API::Instance.extend(shared_api_module)
|
4212
|
+
subject.mount orders_root
|
4213
|
+
end
|
4214
|
+
|
4215
|
+
it 'returns an error when the id is bad' do
|
4216
|
+
get '/v1/orders/abc'
|
4217
|
+
expect(last_response.body).to be_eql('id is invalid')
|
4218
|
+
end
|
4219
|
+
|
4220
|
+
it 'returns the given id when it is valid' do
|
4221
|
+
get '/v1/orders/1-2'
|
4222
|
+
expect(last_response.body).to be_eql('1-2')
|
4223
|
+
end
|
4224
|
+
end
|
4059
4225
|
end
|